In the previous lesson, every time you created a Book object you had to remember to assign
its four properties by hand (Title, Author, Isbn, Available) or use an object
initializer; if you forgot Available, the book was born with false (the default value of
bool) instead of true, with nothing warning you of the mistake. This lesson solves that
problem with constructors: a special method that runs automatically the instant an object
is created with new, guaranteeing that it's always born in a consistent state. Along the
way, you'll meet their counterpart — destructors — and expand BiblioTech's domain model
with two new pieces: Member and Loan, which together with Book will form the core that
the rest of the course is built on.
Contents
- What a constructor is and when it runs
- The default constructor
- Constructors with parameters
- Constructor overloading
- Chaining constructors with
this(...) - Property initializers
- Creating the
Memberclass - Bringing the pieces together: the
Loanclass - Destructors and a mention of
IDisposable
- What a constructor is and when it runs
A constructor is a special method of a class that runs automatically every time an object
is created with new. It's distinguished from a regular method in two ways: it has exactly
the same name as the class, and it declares no return type (not even void).
class Book
{
public string Title { get; set; }
public string Author { get; set; }
public string Isbn { get; set; }
public bool Available { get; set; }
public Book()
{
Available = true;
Console.WriteLine("A new book has been created.");
}
}Book book1 = new Book(); // runs the constructor: prints the message and sets Available = true
Console.WriteLine(book1.Available); // TrueAs soon as new Book() runs, C# allocates memory for the new object and then automatically
runs the constructor's code — in this case, it assigns Available = true and shows a message
— before returning the reference to the already-created object. A constructor is never called
"by hand" like a regular method (book1.Book() would be a compilation error); it only runs as
part of new.
- The default constructor
If a class defines no constructor of its own, C# automatically provides an implicit one,
with no parameters, that does nothing more than create the object with all its properties at
their default value (as you saw in the previous lesson). That's what has been happening,
without you knowing it, in every new Book() from the lessons before this one.
As soon as you define any constructor of your own (like the one in the previous section),
that free, implicit constructor disappears: if you still need to be able to create a
Book with no arguments, you'll have to write that parameterless constructor yourself,
explicitly.
- Constructors with parameters
It's common for a constructor to receive parameters to initialize the object with specific data from the very first moment, instead of creating an "empty" object and filling it in afterward:
class Book
{
public string Title { get; set; }
public string Author { get; set; }
public string Isbn { get; set; }
public bool Available { get; set; }
public Book(string title, string author, string isbn)
{
Title = title;
Author = author;
Isbn = isbn;
Available = true;
}
}Book book1 = new Book("Hopscotch", "Julio Cortazar", "978-84-376-0495-4");
Console.WriteLine(book1.Title); // Hopscotch
Console.WriteLine(book1.Available); // True (always set in the constructor)This constructor guarantees two things at once: that a Book can't be created without
specifying its title, author and ISBN (they are required parameters), and that Available
always starts as true without whoever creates the book having to remember to assign it.
Notice that, inside the constructor, the parameter names (title, lowercase) are different
from the property names (Title, capitalized), precisely so they can be clearly distinguished
when writing Title = title;.
- Constructor overloading
Just like methods, constructors can be overloaded: a single class can have several constructors, as long as they can be distinguished by their parameter list.
class Book
{
public string Title { get; set; }
public string Author { get; set; }
public string Isbn { get; set; }
public bool Available { get; set; }
public Book()
{
Title = "Unknown title";
Author = "Unknown author";
Isbn = "";
Available = true;
}
public Book(string title, string author, string isbn)
{
Title = title;
Author = author;
Isbn = isbn;
Available = true;
}
}Book bookWithNoData = new Book(); // uses the parameterless constructor
Book fullBook = new Book("Ficciones", "Jorge Luis Borges", "978-84-376-0496-1"); // uses the 3-parameter constructorC# automatically picks which constructor to run based on the number and type of arguments you
pass in the new, exactly like the method overloading you saw in the previous lesson.
- Chaining constructors with
this(...)
this(...)The previous example repeats Available = true; in both constructors: if that
initialization logic ever changed, you'd have to remember to update it in both places. To
avoid this duplication, a constructor can call another constructor of the same class using
this(...), delegating the common initialization to it:
class Book
{
public string Title { get; set; }
public string Author { get; set; }
public string Isbn { get; set; }
public bool Available { get; set; }
public Book(string title, string author, string isbn)
{
Title = title;
Author = author;
Isbn = isbn;
Available = true;
}
public Book() : this("Unknown title", "Unknown author", "")
{
// The body can stay empty: this(...) has already done the work
}
}The : this(...) syntax is placed between the constructor's signature and its body ({ }),
and means "before running this constructor, first run the other constructor with these
arguments." That way, new Book() ends up also running the three-parameter constructor's
logic, with default values for each, without duplicating the Available assignment. This
technique is known as constructor chaining, and it's the recommended way to avoid
repeating initialization logic when a class has several constructors.
- Property initializers
For properties that should always start with the same fixed value, regardless of which constructor is used, C# offers an even more direct alternative: assigning the default value in the property's own declaration, without needing to touch any constructor:
class Book
{
public string Title { get; set; }
public string Author { get; set; }
public string Isbn { get; set; }
public bool Available { get; set; } = true; // property initializer
public Book(string title, string author, string isbn)
{
Title = title;
Author = author;
Isbn = isbn;
// No need to assign Available here anymore: it always starts as true
}
}A property initializer runs before the body of any constructor, so if the constructor later assigns a different value to that same property, the constructor's value wins: the initializer only acts as a starting value. It's the simplest and most readable way to set a default value when that value doesn't depend on any constructor parameter.
- Creating the
Member class
Member classWith constructors now under control, it's time to add the second piece of BiblioTech's model:
the Member class, which represents a person registered with the library who has the right
to borrow books.
class Member
{
public int Id { get; set; }
public string Name { get; set; }
public Member(int id, string name)
{
Id = id;
Name = name;
}
public void ShowDetails()
{
Console.WriteLine($"Member #{Id}: {Name}");
}
}Member member1 = new Member(1, "Ana Martinez");
Member member2 = new Member(2, "Luis Gomez");
member1.ShowDetails(); // Member #1: Ana Martinez
member2.ShowDetails(); // Member #2: Luis GomezMember follows exactly the same pattern as Book: auto-implemented properties (Id,
Name), a constructor that requires the essential data when creating it, and a method
(ShowDetails()) that makes use of that data. Id will uniquely identify each member within
BiblioTech — an idea that will gain more importance in Module 5, when persisting data in a
database.
- Bringing the pieces together: the
Loan class
Loan classWith Book and Member already defined, BiblioTech needs to represent the concept that
connects them: a loan, which links a specific book with the member who has borrowed it,
along with the relevant dates. This is the third and final piece of the course's domain
model:
class Loan
{
public Book Book { get; set; }
public Member Member { get; set; }
public DateTime LoanDate { get; set; }
public DateTime? ReturnDate { get; set; } // null while the book hasn't been returned
public Loan(Book book, Member member)
{
Book = book;
Member = member;
LoanDate = DateTime.Now;
ReturnDate = null;
}
public void ShowDetails()
{
string status = ReturnDate is null ? "in progress" : $"returned on {ReturnDate:d}";
Console.WriteLine($"Loan of '{Book.Title}' to {Member.Name} on {LoanDate:d} ({status})");
}
}Notice three important details about this class:
- Its
BookandMemberproperties are not primitive types, but objects of other classes you've already defined: aLoandoesn't duplicate the book's title or the member's name, it instead holds a reference to the originalBookandMemberobjects (remember that classes are reference types). If that book is later marked as unavailable, theLoanthat references it automatically "sees" the change, because it points to the same object. ReturnDateis of typeDateTime?(with?): the question mark indicates a nullable type, needed here because, while the loan is still in progress, there's no actual return date yet.- The constructor sets
LoanDateto the current moment (DateTime.Now) automatically: whoever creates aLoandoesn't have to calculate or pass in the date by hand.
Here's how the three classes are used together, exactly as they'll behave for the rest of the course:
Book book1 = new Book("Hopscotch", "Julio Cortazar", "978-84-376-0495-4");
Member member1 = new Member(1, "Ana Martinez");
book1.Lend(); // the book becomes Available = false (method seen in the previous lesson)
Loan loan1 = new Loan(book1, member1);
loan1.ShowDetails(); // Loan of 'Hopscotch' to Ana Martinez on <today's date> (in progress)With this, BiblioTech's domain model is now made up of three classes that work together:
Book, Member and Loan. This trio will remain stable for the rest of the course, and
you'll keep enriching it (inheritance, encapsulation, collections, persistence...) without
changing its essential purpose.
- Destructors and a mention of
IDisposable
IDisposableJust as a constructor runs when an object is created, a destructor (also called a
finalizer) is a special method that .NET's garbage collector runs, at some undetermined
moment, before finally freeing the memory of an object that's no longer used. It's written
with the ~ symbol followed by the class name, with no access modifier and no parameters:
class Book
{
// ... properties and constructors ...
~Book()
{
Console.WriteLine($"The Book object '{Title}' is being destroyed.");
}
}In practice, destructors are used very rarely in modern C#, for several reasons: you
can't control when they run exactly (it depends on the garbage collector, not your code),
they add a performance overhead, and most of the resource cleanup a destructor could do
(closing files, network or database connections) is nowadays handled with the IDisposable
interface and the using statement, which offer deterministic, explicit control over when
each resource is released. IDisposable will be studied together with file and database
handling in Module 5; for now it's enough to know it exists, and that it's the preferred
alternative to destructors for reliably releasing resources.
| Constructor | Destructor | |
|---|---|---|
| Symbol | None (same name as the class) | ~ before the class name |
| When it runs | When the object is created with new (immediate and predictable) |
At some undetermined moment, decided by the garbage collector |
| Frequency of use in modern C# | Very common | Rare; IDisposable is preferred (Module 5) |
Common Mistakes and Tips
- Defining a constructor with parameters and unknowingly losing the parameterless
constructor: as soon as you add any constructor of your own, the default constructor
disappears; if you still need
new Book()with no arguments, you must declare it yourself. - Repeating initialization logic across several constructors: if two or more constructors
share part of their logic, use
this(...)to chain them instead of copying and pasting the same code in each one. - Confusing the parameter name with the property name:
Title = title;assigns to the property (Title, capitalized) the value of the parameter (title, lowercase); if you mistakenly wrotetitle = title;, nothing useful would happen. - Using
DateTimeinstead ofDateTime?when the value might not exist yet: sinceReturnDatemight have no value while the loan is in progress, it must be a nullable type (DateTime?); using plainDateTimewould force you to invent a "fake" date to represent "not returned yet." - Relying on the destructor to release important resources: since you can't predict when
it will run, it's not a reliable tool for closing files or connections in time; that's what
IDisposableis for (Module 5).
Exercises
-
Define a
Bookclass with the propertiesTitle,Author,Isbn(allstring) andAvailable(bool, initialized via a property initializer totrue). Add a single constructorBook(string title, string author, string isbn)that assigns the three text properties. Create an object and check thatAvailableistruewithout having assigned it in the constructor. -
Add a second parameterless constructor to the previous class that uses
this(...)to chain with the three-parameter constructor, passing the values"Unknown title","Unknown author"and"". Create an object withnew Book()and print itsTitleto verify that the chaining works. -
Create the
Memberclass with the propertiesId(int) andName(string) and a constructorMember(int id, string name). Then create aBookobject (using the constructor from exercise 1) and aMemberobject, and use both to manually build aLoanobject (following the class defined in section 8 of this lesson), showing its details withShowDetails().
Solutions
class Book
{
public string Title { get; set; }
public string Author { get; set; }
public string Isbn { get; set; }
public bool Available { get; set; } = true;
public Book(string title, string author, string isbn)
{
Title = title;
Author = author;
Isbn = isbn;
}
}
Book book1 = new Book("Hopscotch", "Julio Cortazar", "978-84-376-0495-4");
Console.WriteLine(book1.Available); // True
public Book() : this("Unknown title", "Unknown author", "")
{
}
Book emptyBook = new Book();
Console.WriteLine(emptyBook.Title); // Unknown title
The parameterless constructor delegates to the three-parameter one via this(...), so
Title, Author and Isbn end up receiving the specified default values, without
duplicating code.
class Member
{
public int Id { get; set; }
public string Name { get; set; }
public Member(int id, string name)
{
Id = id;
Name = name;
}
}
Book book1 = new Book("Ficciones", "Jorge Luis Borges", "978-84-376-0496-1");
Member member1 = new Member(1, "Ana Martinez");
Loan loan1 = new Loan(book1, member1);
loan1.ShowDetails(); // Loan of 'Ficciones' to Ana Martinez on <today's date> (in progress)
Conclusion
In this lesson you've learned to guarantee that objects are always born in a consistent state
through constructors: the default constructor, constructors with parameters, their
overloading, chaining with this(...), and property initializers; you've also seen what
destructors are and why modern C# prefers IDisposable for releasing resources. With this,
BiblioTech's domain model is now made up of three classes that work together: Book (title,
author, ISBN, availability), Member (identifier and name) and Loan (which book, which
member, when it was borrowed and when it was returned).
This trio of classes, however, currently has some hidden duplication: if BiblioTech wanted to
also lend out magazines, it would have to repeat, in a new Magazine class, the same
properties (Title, Author, Available) and methods (Lend(), Return()) that Book
already has. In the next lesson, Inheritance, you'll learn to avoid that duplication by
extracting the common parts into a shared base class, LibraryItem, from which Book (and
the new Magazine) will inherit.
C# Programming Course
Module 1: Introduction to C#
- Introduction to C#
- Setting Up the Development Environment
- Hello World Program
- Basic Syntax and Structure
- Variables and Data Types
- Arrays and Strings
Module 2: Control Structures
Module 3: Object-Oriented Programming
- Classes and Objects
- Methods
- Constructors and Destructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
- Structs and Records: Value Types and Reference Types
Module 4: Advanced C# Concepts
- Interfaces
- Delegates and Events
- Pattern Matching and Modern C# Features
- Generics
- Collections
- LINQ (Language Integrated Query)
- Asynchronous Programming
Module 5: Working with Data
- File I/O
- Serialization
- Database Connectivity
- Entity Framework
- Working with JSON and Consuming REST APIs
Module 6: Advanced Topics
- Reflection
- Attributes
- Dynamic Programming
- Memory Management and Garbage Collection
- Multithreading and Parallel Programming
Module 7: Building Applications
Module 8: Best Practices and Design Patterns
- Coding Standards and Best Practices
- Design Patterns
- Dependency Injection and Inversion of Control
- Unit Testing
- Code Review and Refactoring
