Properties & Constructors

The last lesson ended with a crack showing: nothing stopped other code from writing account.Balance = -9999; directly, skipping Withdraw entirely. Properties close that crack. Constructors solve a different, equally common problem — making sure an object never exists half-set-up in the first place.

Properties: fields with rules attached

A property looks like a field from the outside — you read and assign it with plain dot syntax — but it's really a pair of methods, get and set, that run every time it's touched. That means you can validate a value on the way in without changing a single line of code that uses the property:

</> Program.cs
class BankAccount
{
    public string Owner;
    private double _balance;

    public double Balance
    {
        get { return _balance; }
        set
        {
            if (value < 0)
            {
                Console.WriteLine("Balance can't go negative — ignoring.");
                return;
            }
            _balance = value;
        }
    }
}

BankAccount account = new BankAccount();
account.Owner = "Jamie";
account.Balance = 100;
account.Balance = -50;
Console.WriteLine(account.Balance);
Output
Balance can't go negative — ignoring.
100

Callers still write account.Balance = -50; — the syntax hasn't changed at all — but now that assignment runs through set, which rejects it. _balance is the private field actually holding the value; Balance is the public property guarding access to it. That underscore-prefixed private backing field is a very common C# naming convention.

Auto-properties: the shortcut for the common case

Writing a private backing field for every property gets repetitive, and most properties don't need custom validation logic. For those, C# lets you skip the backing field entirely:

</> Program.cs
class Product
{
    public string Name { get; set; }
    public double Price { get; set; }
}

Product item = new Product();
item.Name = "Coffee";
item.Price = 4.50;
Console.WriteLine($"{item.Name}: ${item.Price}");
Output
Coffee: $4.5

{ get; set; } is an auto-property — the compiler generates the hidden backing field for you. It behaves exactly like a public field from the caller's side, but because it's a property, you can drop in real get/set logic later without breaking any code that already uses it. A plain public field can never make that upgrade without a breaking change.

Note: you can also make a property read-only from outside the class with public double Price { get; private set; } — anyone can read Price, but only code inside Product can assign it. This is a common pattern for values that should be set once, internally, and never touched from outside.

Constructors: guaranteeing valid setup

So far, building an object has meant new BankAccount() followed by several lines setting fields one at a time — and nothing stops you from forgetting one, leaving an account with no owner. A constructor runs automatically when an object is created and can require the values it needs up front:

</> Program.cs
class BankAccount
{
    public string Owner { get; set; }
    public double Balance { get; set; }

    public BankAccount(string owner, double startingBalance)
    {
        Owner = owner;
        Balance = startingBalance;
        Console.WriteLine($"Opened account for {Owner} with ${Balance}");
    }
}

BankAccount account = new BankAccount("Jamie", 100);
Console.WriteLine(account.Balance);
Output
Opened account for Jamie with $100
100

A constructor has the same name as the class and no return type, not even void. Once BankAccount defines BankAccount(string owner, double startingBalance), the parameterless new BankAccount() from the earlier examples stops compiling entirely — C# only gives you a free do-nothing constructor if you haven't defined any constructor yourself. Defining this one is what makes an owner and a starting balance mandatory for every account that gets created.

Constructor overloading

Like methods, constructors can be overloaded — multiple constructors with different parameter lists, so callers can choose how much they need to specify:

</> Program.cs
class BankAccount
{
    public string Owner { get; set; }
    public double Balance { get; set; }

    public BankAccount(string owner, double startingBalance)
    {
        Owner = owner;
        Balance = startingBalance;
    }

    public BankAccount(string owner) : this(owner, 0)
    {
    }
}

BankAccount a = new BankAccount("Jamie", 100);
BankAccount b = new BankAccount("Sasha");
Console.WriteLine($"{a.Owner}: {a.Balance}");
Console.WriteLine($"{b.Owner}: {b.Balance}");
Output
Jamie: 100
Sasha: 0

: this(owner, 0) forwards to the two-parameter constructor with 0 filled in as the starting balance, so the "open with just a name" logic lives in exactly one place instead of being duplicated.