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

  1. What a constructor is and when it runs
  2. The default constructor
  3. Constructors with parameters
  4. Constructor overloading
  5. Chaining constructors with this(...)
  6. Property initializers
  7. Creating the Member class
  8. Bringing the pieces together: the Loan class
  9. Destructors and a mention of IDisposable

  1. 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); // True

As 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.

  1. 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.

  1. 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;.

  1. 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 constructor

C# 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.

  1. Chaining constructors with 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.

  1. 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.

  1. Creating the Member class

With 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 Gomez

Member 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.

  1. Bringing the pieces together: the Loan class

With 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 Book and Member properties are not primitive types, but objects of other classes you've already defined: a Loan doesn't duplicate the book's title or the member's name, it instead holds a reference to the original Book and Member objects (remember that classes are reference types). If that book is later marked as unavailable, the Loan that references it automatically "sees" the change, because it points to the same object.
  • ReturnDate is of type DateTime? (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 LoanDate to the current moment (DateTime.Now) automatically: whoever creates a Loan doesn'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.

  1. Destructors and a mention of IDisposable

Just 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 wrote title = title;, nothing useful would happen.
  • Using DateTime instead of DateTime? when the value might not exist yet: since ReturnDate might have no value while the loan is in progress, it must be a nullable type (DateTime?); using plain DateTime would 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 IDisposable is for (Module 5).

Exercises

  1. Define a Book class with the properties Title, Author, Isbn (all string) and Available (bool, initialized via a property initializer to true). Add a single constructor Book(string title, string author, string isbn) that assigns the three text properties. Create an object and check that Available is true without having assigned it in the constructor.

  2. 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 with new Book() and print its Title to verify that the chaining works.

  3. Create the Member class with the properties Id (int) and Name (string) and a constructor Member(int id, string name). Then create a Book object (using the constructor from exercise 1) and a Member object, and use both to manually build a Loan object (following the class defined in section 8 of this lesson), showing its details with ShowDetails().

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.

© Copyright 2026. All rights reserved