C# Properties and Constructors

Expose object data safely with properties, validate values in setters, make data read-only with init and private set, compute values on the fly, and set objects up correctly with constructors, required members and primary constructors.

Lesson 12 of 43Intermediate17 min read

In the last lesson, objects kept their data in private fields and offered methods like GetBalance(). That works, but C# has a better tool built for exactly this job: properties. A property looks like a field from the outside (order.Total), but behind the scenes it runs code, so it can validate new values, compute results, or allow reading but not writing.

Properties are everywhere in C#. list.Count, text.Length and DateTime.Now are all properties. Frameworks such as ASP.NET Core and Entity Framework rely on them to read and write your objects automatically, for example when turning JSON into a C# object.

The other half of a well-designed class is the constructor, the special method that runs when an object is created with new. A constructor makes sure every object starts life in a valid state, with its required data filled in. In this lesson you'll write properties of every kind, constructors with overloads and chaining, and use modern features like init, required and primary constructors.

Auto-properties

C#
public class Product
{
    public string Name { get; set; } = "";
    public decimal Price { get; private set; }
    public DateTime Created { get; } = DateTime.Now;
    public string Sku { get; init; } = "";
}
  • { get; set; } creates a property with a hidden field; anyone can read and write it.
  • private set lets everyone read but only the class itself write.
  • { get; } with no setter can only be set in the constructor or initializer: it's read-only afterwards.
  • init can be set in an object initializer (new Product { Sku = "A1" }) but never changed after that.
  • Properties use PascalCase, like methods.

Validation and computed properties

When you need rules, write the accessors yourself, with a private backing field:

C#
private int quantity;
public int Quantity
{
    get => quantity;
    set
    {
        if (value < 0) throw new ArgumentOutOfRangeException(nameof(value));
        quantity = value;
    }
}

value is the new value being assigned. A computed property has only a getter and calculates its result each time it's read: public decimal Total => Price * Quantity;. It never gets out of date because nothing is stored.

Constructors

A constructor has the same name as the class and no return type:

C#
public class Order
{
    public string Customer { get; }
    public List<string> Items { get; } = new();

    public Order(string customer)
    {
        if (string.IsNullOrWhiteSpace(customer))
            throw new ArgumentException("Customer is required");
        Customer = customer;
    }

    public Order() : this("Guest") { }   // chains to the other constructor
}
  • If you don't write a constructor, C# provides an empty one.
  • Overloaded constructors offer different ways to create an object; : this(...) reuses another constructor's logic.
  • Primary constructors (C# 12) put parameters on the class itself: public class Order(string customer) { public string Customer { get; } = customer; }.
  • The required modifier, as in public required string Email { get; init; }, makes the compiler insist that callers set the property in the initializer.

C# Object-Oriented Programming Syntax

Properties and constructors

public string Name { get; set; } = "";
public int Id { get; init; }
public decimal Balance { get; private set; }
public required string Email { get; init; }
public bool IsEmpty => Count == 0;
private int age;
public int Age { get => age; set => age = value >= 0 ? value : 0; }
public Person(string name) { Name = name; }
public Person() : this("Unknown") { }
public class Point(int x, int y) { public int X => x; }
var p = new Person("Ada") { Id = 7 };

C# Object-Oriented Programming Summary

  • Properties look like fields but run get and set code.
  • { get; set; } auto-properties suit simple data; private set and init limit writing.
  • Setters can validate value; computed properties calculate on every read.
  • Constructors share the class name, run on new, and set up valid objects.
  • this(...) chains constructors; required forces callers to set a property.

C# Object-Oriented Programming Examples

Each example shows the code, the output it produces and what each part does.

Example 1

A product with validation and a computed total

C#
var line = new OrderLine("Notebook", 3.50m) { Quantity = 4 };
Console.WriteLine($"{line.Name} x{line.Quantity} = {line.Total}");

line.Quantity = 2;
Console.WriteLine(line.Total);

try
{
    line.Quantity = -1;
}
catch (ArgumentOutOfRangeException)
{
    Console.WriteLine("Quantity can't be negative");
}

class OrderLine
{
    public string Name { get; }
    public decimal UnitPrice { get; }
    private int quantity = 1;

    public int Quantity
    {
        get => quantity;
        set
        {
            if (value < 0) throw new ArgumentOutOfRangeException(nameof(value));
            quantity = value;
        }
    }

    public decimal Total => UnitPrice * Quantity;

    public OrderLine(string name, decimal unitPrice)
    {
        Name = name;
        UnitPrice = unitPrice;
    }
}
Output
Notebook x4 = 14.00
7.00
Quantity can't be negative
Line-by-line explanation
  1. Line 1The constructor sets the name and price, then the initializer sets Quantity through its setter.
  2. Lines 4 to 5Total is recalculated each time, so it always matches the current quantity.
  3. Line 9The setter rejects the invalid value by throwing an exception.
  4. Lines 18 to 19Get-only properties can be set in the constructor and never changed afterwards.
  5. Lines 22 to 30A full property with a backing field and validation.
  6. Line 32A computed property stores nothing.
  7. Lines 34 to 38The constructor makes sure every line starts with a name and a price.
Example 2

init, required and constructor chaining

C#
var ada = new Member { Email = "ada@example.com", Name = "Ada" };
var guest = new Member { Email = "guest@example.com" };
Console.WriteLine(ada);
Console.WriteLine(guest);

var room = new Room();
var hall = new Room("Main hall", 120);
Console.WriteLine($"{room.Name} seats {room.Capacity}");
Console.WriteLine($"{hall.Name} seats {hall.Capacity}");

class Member
{
    public required string Email { get; init; }
    public string Name { get; init; } = "Guest";
    public DateOnly Joined { get; } = new DateOnly(2026, 1, 15);

    public override string ToString() => $"{Name} <{Email}> since {Joined:yyyy-MM-dd}";
}

class Room
{
    public string Name { get; }
    public int Capacity { get; }

    public Room(string name, int capacity)
    {
        Name = name;
        Capacity = capacity;
    }

    public Room() : this("Meeting room", 8) { }
}
Output
Ada <ada@example.com> since 2026-01-15
Guest <guest@example.com> since 2026-01-15
Meeting room seats 8
Main hall seats 120
Line-by-line explanation
  1. Lines 1 to 2required forces Email to be set; Name falls back to its default.
  2. Line 13Leaving out Email would be a compile error, not a run-time surprise.
  3. Line 14init allows setting the value in an initializer, then locks it.
  4. Line 17:yyyy-MM-dd formats the date.
  5. Lines 25 to 29The main constructor sets both properties.
  6. Line 31The parameterless constructor chains to the main one with default values.
Example 3

A primary constructor

C#
var account = new SavingsAccount("Grace", 0.03m);
account.Deposit(1000m);
account.AddInterest();
Console.WriteLine($"{account.Owner}: {account.Balance:F2}");

class SavingsAccount(string owner, decimal rate)
{
    public string Owner { get; } = owner;
    public decimal Balance { get; private set; }

    public void Deposit(decimal amount) => Balance += amount;
    public void AddInterest() => Balance += Balance * rate;
}
Output
Grace: 1030.00
Line-by-line explanation
  1. Line 6Primary constructor parameters are listed after the class name.
  2. Line 8A property is initialised from a parameter.
  3. Line 9private set means only the class can change the balance.
  4. Line 12Parameters such as rate can be used anywhere in the class body.

Common C# Object-Oriented Programming Mistakes

A public setter that lets data go invalid

Don't
public int Age { get; set; }          // anyone can set Age = -40
Do
private int age;
public int Age
{
    get => age;
    set => age = value is >= 0 and <= 150 ? value : throw new ArgumentOutOfRangeException(nameof(value));
}

A property that calls itself

Don't
public string Name
{
    get => Name;                      // StackOverflowException: the getter calls itself forever
    set => Name = value;
}
Do
public string Name { get; set; } = "";

Storing a value that should be computed

Don't
public decimal Total { get; set; }    // must be updated by hand whenever Price or Quantity changes
Do
public decimal Total => Price * Quantity;

C# Object-Oriented Programming in Real Projects

Properties in .NET frameworks

ASP.NET Core binds JSON request bodies to classes with public properties, and validation attributes like [Required] and [Range(1, 10)] sit on those properties. Entity Framework Core maps each property to a database column. Modern codebases use required and init for data that's set once, and keep setters private on domain objects so changes go through methods that enforce business rules.

C# Object-Oriented Programming FAQ

What is the difference between init and set in C#?

A set accessor allows the property to be changed at any time. An init accessor only allows it to be set while the object is being created, in a constructor or an object initializer such as new Product { Sku = "A1" }. After that, the property is read-only.

What does the required keyword do in C#?

required on a property, as in public required string Email { get; init; }, makes the compiler report an error if code creates the object without setting that property in an object initializer. It's useful for objects built with initializers rather than constructors, so mandatory values can't be forgotten.

What is a primary constructor in C#?

Since C# 12, a class can declare constructor parameters right after its name: class Order(string customer). The parameters are available throughout the class body, for example to initialise properties. Unlike in a record, they don't automatically become public properties, so expose them yourself when other code needs them.

When should I use a property instead of a method?

Use a property for something that behaves like data: cheap to read, with no surprising side effects, such as Name or Total. Use a method for actions, slow work or anything that needs parameters, such as CalculateShipping(address). A good rule is that reading a property twice in a row should give the same answer.

Can a class have more than one constructor?

Yes. Constructors can be overloaded with different parameter lists, and one can call another with : this(...) to avoid repeating validation. If you write any constructor yourself, C# no longer provides the empty parameterless one, so add it explicitly if you still need it.

C# Object-Oriented Programming Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the property so it can be read and written.

public string Title { get; ; }
Multiple choiceeasy

What is the name of a constructor?

Predict the outputmedium

What does this print?

var r = new Rect { Width = 3, Height = 4 };
Console.WriteLine(r.Area);
class Rect { public int Width { get; init; } public int Height { get; init; } public int Area => Width * Height; }
Multiple choicemedium

What does { get; private set; } allow?

Multiple choicehard

What does the required modifier do?

C# Object-Oriented Programming Quiz

5 questions to check what you learned.

  1. What does an init accessor allow?

    • Setting the value only during object creation
    • Changing the value any time
    • Reading only
    • Nothing
  2. What is value inside a setter?

    • The old value
    • The new value being assigned
    • The property name
    • A keyword for null
  3. What does : this("Guest") after a constructor do?

    • Calls the base class
    • Creates a new object
    • Calls another constructor of the same class first
    • Sets a property called this
  4. When is a computed property like Total => Price * Qty calculated?

    • Every time it's read
    • Once, when the object is created
    • Never
    • Only in the constructor
  5. Where are primary constructor parameters written?

    • Inside a method called Primary
    • In the Main method
    • In attributes
    • In brackets after the class name