Magical C# Constructors

Ellie - Nov 6 '23 - - Dev Community

Hello! Welcome back to another magical post about Object Oriented Programming with C#.

This post will introduce the concept of constructors and how they are helpful in creating objects.

The main topics discussed are:

  • Default values
  • Default constructors
  • Constructor overloading
  • The 'this' keyword
  • The 'base' keyword

Assuming you have already read my post about C# classes and objects, let's move on to the wizard examples!🧙🏻‍♀️

Constructors

First, it's important to answer the question: what is a constructor?

  • A constructor is a special method in a class that sets up and initializes a new object, ensuring that all its properties are given the right starting values.

If you want more info about constructors, it's beneficial to get a few different examples & points of view. Here are some good resources to learn more:


Wizard Class without a constructor

Remember our "Wizard" class from the last post? If not, that's ok, here it is:

public class Wizard
{
    private int age;

    // Properties
    public string Name { get; set; }
    public string House { get; set; }
    public string Pet { get; set; }

    public int Age
    {
        get 
        { 
            return age; 
        }
        set 
        {
            if (value >= 11 && value <= 150)
            {
                age = value;
            }
            else
            {
                Console.WriteLine("Invalid age for a wizard at Hogwarts.");
            }
        }
    }
    public string WandType { get; set; }

    // Methods
    public void CastSpell(string spellName)
    {
        Console.WriteLine($"{Name} casts {spellName}!");
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, if we wanted to add a new "Wizard" we would create a new "draco" object like this:

Wizard draco = new Wizard
{
    Name = "Draco Malfoy",
    House = "Slytherin",
    Age = 11,
    WandType = "Hawthorn with a unicorn hair core",
    Pet = "Eagle Owl"
};
Enter fullscreen mode Exit fullscreen mode

But there's an easier way! and that's why we use constructors.

Wizard class with a constructor

Here's how the Wizard class would look with a constructor:

public class Wizard
{
    private int age;

    public string Name { get; set; }
    public string House { get; set; }
    public string Pet { get; set; }

    public int Age
    {
        get 
        { 
            return age; 
        }
        set 
        {
            if (value >= 11 && value <= 150)
            {
                age = value;
            }
            else
            {
                Console.WriteLine("Invalid age for a wizard at Hogwarts.");
            }
        }
    }
    public string WandType { get; set; }

    // constructor 
    public Wizard(string name, string house, int age, string wandType, string pet)
    {
        Name = name;
        House = house;
        Age = age;
        WandType = wandType;
        Pet = pet;
    }

    // Method to represent casting a spell
    public void CastSpell(string spellName)
    {
        Console.WriteLine($"{Name} casts {spellName}!");
    }
}
Enter fullscreen mode Exit fullscreen mode

This makes it much easier to create a new "Wizard" aka a new object:

Wizard draco = new Wizard("Draco Malfoy", "Slytherin", 11, "Hawthorn with a unicorn hair core", "Eagle Owl");
Enter fullscreen mode Exit fullscreen mode

Default values

But what if we don't know everything about a new "Wizard" at Hogwarts?

Could we just, not enter that info?

Well, if we did that, this is what would happen.

First, we have our constructor:

public Wizard(string name, string house, int age, string wandType, string pet)
    {
        Name = name;
        House = house;
        Age = age;
        WandType = wandType;
        Pet = pet;
    }
Enter fullscreen mode Exit fullscreen mode

Then we try to create a new Wizard:

Wizard neville = new Wizard("Neville Longbottom", "Gryffindor", 11, "Cherrywood with a unicorn hair core");
// Missing the 'pet' parameter
Enter fullscreen mode Exit fullscreen mode

So.. what happens if we don't include Neville's pet toad?

We will see this error 😱

error CS7036: There is no argument given that corresponds to the required formal parameter 'pet' of 'Wizard.Wizard(string, string, int, string, string)'
Enter fullscreen mode Exit fullscreen mode

So.. how can we avoid this? 🙄

Well, that's what default values are for!

// look! some default values!
public Wizard(string name = "Unknown", string house = "Unassigned", int age = 11, string wandType = "Standard", string pet = "Unknown")
    {
        Name = name;
        House = house;
        Age = age;
        WandType = wandType;
        Pet = pet;
    }
Enter fullscreen mode Exit fullscreen mode

This way, if we choose to omit, or forget to assign a value, the code will still run with the value being assigned to the default value.

Default constructors

Another important topic is default constructors although they sound similar to default values, they serve a different purpose.

They are the simplest form of constructors which don't take any arguments.

In some cases, a default constructor might set properties to their default values, or it might leave them to be set later.

For example, if you want to create a "Wizard" but you don't have all the necessary info, a default constructor allows you to instantiate the object and fill in the details later:

public Wizard()
{
    // Initialize with default values
    Name = "Unknown";
    House = "Unassigned";
    Age = 11; // Default starting age for Hogwarts students
    WandType = "Standard";
    Pet = "Unknown"; 
}
Enter fullscreen mode Exit fullscreen mode

Then you can create a new "Wizard" like this:

Wizard ginny = new Wizard();
Enter fullscreen mode Exit fullscreen mode

Since "ginny" is a new student, we still don't have all of her details. They must be assigned as they become known to the school.

// When Ginny is enrolled, we know her full name
ginny.Name = "Ginny Weasley";

// Then we assign the rest of the properties as we become aware of them

// After the sorting hat ceremony we find out she is sorted into Gryffindor!
ginny.House = "Gryffindor";

// Then she must register her wand
ginny.WandType = "Yew with a phoenix feather core";

// Later, when she gets a pet, we would update her pet info
ginny.Pet = "Arnold the Pygmy Puff";
Enter fullscreen mode Exit fullscreen mode

But this seems a bit tedious, why would I ever use this method over the previously described methods?

Well, a default constructor can be particularly useful when you need to create an object without immediately supplying detailed information, it provides the ability to set properties at a later time.

This is exactly what we did with "ginny", we didn't have all of the information at first so we had to add the new information as we became aware of it.

Constructor overloading

Similar to default values and default constructors, there is something called constructor overloading. We do this when we might not have all the details about a wizard, or maybe we have wizards with different sets of known attributes.

So.. what's the difference between all three?

Default values and default constructors are useful when you want to fill in missing information automatically. However, constructor overloading makes it possible to create objects with varying sets of initial information from the very beginning.

public class Wizard
{
    public string Name { get; set; }
    public string House { get; set; }
    public string Pet { get; set; }
    public string WandType { get; set; }
    // Intentionally excluding Age for readability

    // Constructor for a wizard without a pet
    public Wizard(string name, string house, string wandType)
    {
        Name = name;
        House = house;
        WandType = wandType;
    }

    // Constructor for a wizard with a pet
    public Wizard(string name, string house, string wandType, string pet)
    {
        Name = name;
        House = house;
        WandType = wandType;
        Pet = pet;
    }

    // Constructor for a foreign wizard where we only know the name and wand type
    public Wizard(string name, string wandType)
    {
        Name = name;
        WandType = wandType;
        // House and pet might be unknown
    }
}

// Wizard with a pet
Wizard hermione = new Wizard("Hermione Granger", "Gryffindor", "Vine wood with a dragon heartstring core", "Crookshanks");
// Foreign wizard
Wizard viktor = new Wizard("Viktor Krum", "Hornbeam");
Enter fullscreen mode Exit fullscreen mode

Can you use default values, default constructors, and constructor overloading at the same time?

Yes! At the end of this post, there will be an example of an ultimate "Wizard" class using everything mentioned in this post.

'this' keyword

What's this?

Well, the "this" keyword has two main usecases.

1. To avoid confusion ( this.Name = name; )

public class Wizard
{
    public string Name { get; set; }

    // Constructor using the 'this' keyword to differentiate between
    // the parameter 'name' and the class property 'Name'
    public Wizard(string name)
    {
        this.Name = name; // 'this.Name' refers to the property 'Name' of the current instance
        // Name = name; can get confusing, so using this.Name can be a bit clearer sometimes
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Constructor chaining ( : this(name) )

public class Wizard
{
    public string Name { get; set; }
    public string House { get; set; }
    public string WandType { get; set; }

    // Constructor that takes only Name
    public Wizard(string name)
    {
        this.Name = name;
    }

    // Constructor that takes Name and House, reusing the first constructor
    public Wizard(string name, string house) : this(name)
    {
        this.House = house;
    }

    // Constructor that takes Name, House, and WandType, reusing the second constructor
    public Wizard(string name, string house, string wandType) : this(name, house)
    {
        this.WandType = wandType;
    }
}
Enter fullscreen mode Exit fullscreen mode

What's the point of constructor chaining?

Constructor chaining allows a constructor to call another constructor within the same class to set things up, which keeps the code simple and avoids repetition. Neat, right?

'base' keyword

Another keyword?? 🤨

Yes.. but this one is pretty easy!

It's very similar to the "this" keyword, except it can help chain info from other classes.

According to our previous examples, the "Wizard" class represents all students or faculty members at Hogwarts. However, students may also participate in school sports, like Quidditch, which requires keeping track of additional details like the player's position.

So we would also need to create a "QuidditchPlayer" class that extends the Wizard class because it needs to include all the standard wizard information, plus these extra sports-related attributes.

This is how that would look:

// Base class for any wizard at Hogwarts
public class Wizard
{
    public string Name { get; set; }
    public string House { get; set; }

    public Wizard(string name, string house)
    {
        Name = name;
        House = house;
    }
}

// Derived class for a Quidditch player
public class QuidditchPlayer : Wizard
{
    public string Position { get; set; }

    // Constructor sets up a Quidditch player, using 'base' to include the basic wizard setup info
    // The 'base' keyword passes name and house to the Wizard constructor
    public QuidditchPlayer(string name, string house, string position) : base(name, house) 
    {
        Position = position;
    }
}

QuidditchPlayer harry = new QuidditchPlayer("Harry Potter", "Gryffindor", "Seeker");
Enter fullscreen mode Exit fullscreen mode

That's it for basic constructors. Here are all of the things we learned together in an ultimate "Wizard" class!

ULTIMATE WIZARD CLASS!

public class Wizard
{
    public string Name { get; set; }
    public string House { get; set; }
    public string WandType { get; set; }

    // Default constructor
    public Wizard()
    {
        Name = "Unknown";
        House = "Unassigned";
        WandType = "Standard";
    }

    // 1. Constructor that takes only Name but inherits the defaults from the default constructor
    public Wizard(string name) : this()
    {
        Name = name;
    }

    // 2. Constructor that takes Name and House, reusing constructor 1
    public Wizard(string name, string house) : this(name)
    {
        House = house;
    }

    // 3. Constructor that takes Name, House, and WandType, reusing constructor 2
    public Wizard(string name, string house, string wandType) : this(name, house)
    {
        WandType = wandType;
    }
}

public class QuidditchPlayer : Wizard
{
    public string Position { get; set; }

    // Default constructor for QuidditchPlayer
    public QuidditchPlayer() : base()
    {
        Position = "Chaser";
    }

    // Constructor chaining is used here as well, reusing Wizard's constructor
    public QuidditchPlayer(string name, string house, string wandType, string position) : base(name, house, wandType)
    {
        Position = position;
    }
}

// A lesser-known wizard with minimal information
Wizard blaise = new Wizard("Blaise Zabini");

// A member of the Order of the Phoenix with some details known
Wizard mundungus = new Wizard("Mundungus Fletcher", "Gryffindor");

// A fully detailed wizard
Wizard hermione = new Wizard("Hermione Granger", "Gryffindor", "Vine wood with a dragon heartstring core");

// Quidditch player with all properties known
QuidditchPlayer harry = new QuidditchPlayer("Harry Potter", "Gryffindor", "Holly wood with a phoenix feather core", "Seeker");

// Example output:
Console.WriteLine($"Wizard: {blaise.Name}, House: {blaise.House}, Wand: {blaise.WandType}");
// Output: Wizard: Blaise Zabini, House: Unassigned, Wand: Standard

Console.WriteLine($"Order Member: {mundungus.Name}, House: {mundungus.House}, Wand: {mundungus.WandType}");
// Output: Order Member: Mundungus Fletcher, House: Gryffindor, Wand: Standard

Console.WriteLine($"Wizard: {hermione.Name}, House: {hermione.House}, Wand: {hermione.WandType}");
// Output: Wizard: Hermione Granger, House: Gryffindor, Wand: Vine wood with a dragon heartstring core

Console.WriteLine($"Quidditch Player: {harry.Name}, House: {harry.House}, Wand: {harry.WandType}, Position: {harry.Position}");
// Output: Quidditch Player: Harry Potter, House: Gryffindor, Wand: Holly wood with a phoenix feather core, Position: Seeker
Enter fullscreen mode Exit fullscreen mode

Challenge (optional)

If you're up for it, I challenge you to make your own constructor that includes:

  • Default values
  • A default constructor
  • Constructor overloading
  • The 'this' keyword
  • The 'base' keyword

Theme ideas:

  • Astronomy: Classes for celestial objects like Star, Planet, and Asteroid. Properties could include mass, composition, and orbit details.
  • Magical Beasts: A MagicalBeast base class with properties like rarity, danger level, and magical abilities. Subclasses could include Dragon, Phoenix, Basilisk, each with special traits or abilities.

Don't be afraid to ask your friendly neighborhood chatGPT for more ideas that align with your personal interests😄

If you actually do the challenge, please send me the link to your code. I would love to see your solution!


Well, that's it for this post! If you've made it this far, Hermione certainly would be proud of you.

In the future, I will be making part 2 which will be about "static constructors", "copy constructors", and "destructors". (Thought it would be a bit overwhelming to add those here)

If you're looking for more resources to learn how to code check out my Free Coding Resources site, it has links to a bunch of my favorite courses/resources!

If you have any questions or want to connect, the best place to reach me is on Twitter/X.

Happy coding!

. . . . . . . . .