Properties — Encapsulation with Getters and Setters

Properties are the controlled access points to a class’s data. They look like fields at the call site (post.Title = "Hello") but are actually methods under the hood — the compiler generates a get_Title() and set_Title() method pair. This means you can add validation, transformation, computation, or notifications to a property at any time without changing any code that reads or writes it. Properties are what make C# classes both convenient and robust.

Auto-Implemented Properties

public class User
{
    // Auto-property — compiler generates a hidden backing field
    public int    Id       { get; set; }
    public string Name     { get; set; } = string.Empty;  // default value
    public string Email    { get; set; } = string.Empty;
    public bool   IsActive { get; set; } = true;

    // Read-only auto-property — can only be set in constructor or initialiser
    public DateTime CreatedAt { get; } = DateTime.UtcNow;

    // Init-only property (C# 9+) — settable in object initialiser, then immutable
    public string Role { get; init; } = "User";
}
Note: The init accessor (C# 9+) allows a property to be set only during object initialisation — either in the constructor or in an object initialiser expression (new User { Role = "Admin" }). After the object is constructed, the property is read-only. This is ideal for DTOs and domain entities where the value should be set once and never changed. Entity Framework Core fully supports init properties for mapping.
Tip: Always initialise string properties to string.Empty (or a sensible default) in the property declaration: public string Name { get; set; } = string.Empty. With <Nullable>enable</Nullable> in the .csproj, a non-nullable string property without an initialiser or constructor assignment causes a compiler warning. Initialising to string.Empty satisfies the compiler, documents your intent, and prevents null-reference errors from unset properties in model binding.
Warning: Entity Framework Core uses properties (not fields) to map columns. If a property has a private setter ({ get; private set; }), EF Core can still set it via reflection during materialisation — but the property must exist and be a real property, not a field. If you want full EF Core compatibility with private setters, use the protected modifier instead of private, or use the init accessor which EF Core supports natively from EF 7+.

Properties with Custom Logic

public class Product
{
    private string _name = string.Empty;
    private decimal _price;

    // Custom get and set — validation in setter
    public string Name
    {
        get => _name;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("Product name cannot be empty.");
            _name = value.Trim();
        }
    }

    // Price with validation
    public decimal Price
    {
        get => _price;
        set => _price = value >= 0
            ? value
            : throw new ArgumentOutOfRangeException(nameof(value), "Price cannot be negative.");
    }

    // Computed property — no backing field needed
    public decimal PriceWithVat => _price * 1.20m;   // 20% VAT

    // Private setter — external code can read but not write directly
    public int StockLevel { get; private set; }

    public void AddStock(int quantity)
    {
        if (quantity <= 0) throw new ArgumentException("Quantity must be positive.");
        StockLevel += quantity;
    }
}

Object Initialisers

// Object initialiser syntax — set properties after construction
var user = new User
{
    Id     = 1,
    Name   = "Alice",
    Email  = "alice@example.com",
    Role   = "Admin",   // init-only property — must use object initialiser
};

// Equivalent to:
// var user = new User();
// user.Id    = 1;
// user.Name  = "Alice";
// user.Email = "alice@example.com";
// user.Role  = "Admin"; ← can't do this after init — init-only!

// Used throughout ASP.NET Core for DTO construction and test data setup

Common Mistakes

Mistake 1 — Not initialising string properties (nullable warning or null ref)

❌ Wrong — compiler warning with nullable enabled; possible NullReferenceException:

public class User { public string Name { get; set; } }   // warning: not initialised

✅ Correct:

public class User { public string Name { get; set; } = string.Empty; }

Mistake 2 — Using a public field when a property is needed

❌ Wrong — model binding and EF Core do not work with public fields:

public string Title;   // field — no get/set

✅ Correct:

public string Title { get; set; } = string.Empty;   // property — works everywhere

🧠 Test Yourself

What is the difference between { get; set; }, { get; }, and { get; init; }?