C# properties — get, set, init, auto properties, and computed properties

Properties are the standard way to expose data in C# classes. They look like fields from the caller's side but let you add logic, validation, or access control behind the scenes.

Auto Properties

An auto property lets the compiler generate the backing field for you. Use it when you don't need custom logic in the getter or setter.

C# Example Code
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

var p = new Person();
p.Name = "Alice";
p.Age  = 30;
Console.WriteLine($"{p.Name}, {p.Age}"); // Alice, 30

Auto Property Initializers

Set a default value without a constructor.

C# Example Code
public class Config
{
    public int MaxRetries { get; set; } = 3;
    public string Environment { get; set; } = "Production";
    public List<string> AllowedHosts { get; set; } = new();
}

var cfg = new Config();
Console.WriteLine(cfg.MaxRetries);    // 3
Console.WriteLine(cfg.Environment);  // Production

Read-Only Properties (getter only)

A property with only a getter can only be set in the constructor or with an initializer.

C# Example Code
public class Circle
{
    public double Radius { get; }

    public Circle(double radius)
    {
        Radius = radius;  // Only allowed here
    }
}

var c = new Circle(5.0);
Console.WriteLine(c.Radius); // 5
// c.Radius = 10; // Compile error

init-Only Properties (C# 9+)

init allows the property to be set during object initialization but not afterwards — useful for object initializer syntax without full mutability.

C# Example Code
public class Order
{
    public int Id    { get; init; }
    public string Customer { get; init; } = "";
    public decimal Total   { get; init; }
}

// Set during initialization — OK
var order = new Order { Id = 1, Customer = "Alice", Total = 99.99m };
Console.WriteLine($"#{order.Id} {order.Customer}: {order.Total:C}");

// order.Id = 2; // Compile error — init-only after construction

Asymmetric Access Modifiers

Give the getter a wider access level than the setter.

C# Example Code
public class Counter
{
    public int Value { get; private set; }  // public read, private write

    public void Increment() => Value++;
    public void Reset()     => Value = 0;
}

var c = new Counter();
c.Increment();
c.Increment();
Console.WriteLine(c.Value); // 2
// c.Value = 10; // Compile error — setter is private

Properties with Backing Fields and Validation

When you need logic in the setter (e.g., validation, change notification), write the getter and setter explicitly with a backing field.

C# Example Code
public class Temperature
{
    private double _celsius;

    public double Celsius
    {
        get => _celsius;
        set
        {
            if (value < -273.15)
                throw new ArgumentOutOfRangeException(nameof(value),
                    "Temperature cannot be below absolute zero.");
            _celsius = value;
        }
    }

    // Computed property — no backing field needed
    public double Fahrenheit => _celsius * 9 / 5 + 32;
}

var t = new Temperature();
t.Celsius = 100;
Console.WriteLine(t.Fahrenheit); // 212

Computed Properties

A computed property derives its value from other data. It has only a getter and no backing field.

C# Example Code
public class Rectangle
{
    public double Width  { get; set; }
    public double Height { get; set; }

    // Computed — no storage, always recalculated
    public double Area      => Width * Height;
    public double Perimeter => 2 * (Width + Height);
}

var r = new Rectangle { Width = 5, Height = 3 };
Console.WriteLine($"Area: {r.Area}, Perimeter: {r.Perimeter}");
// Area: 15, Perimeter: 16

Property vs Field

PropertyField
Syntaxpublic int Age { get; set; }public int Age;
Validation logicYesNo
Can be in an interfaceYesNo
Supports data bindingYesNo
Breaking change if added laterNoYes (changes binary)
Recommended for public APIYesNo

Fields are an implementation detail — keep them private. Expose data via properties.

C# Example Code
// Bad — public field
public class Bad
{
    public int Count; // no control, no validation
}

// Good — public property
public class Good
{
    public int Count { get; private set; }
    public void Increment() => Count++;
}

Expression-Bodied Properties

For simple getters (and setters), use the expression-body shorthand.

C# Example Code
public class FullName
{
    public string First { get; set; } = "";
    public string Last  { get; set; } = "";

    // Expression-bodied getter
    public string Display => $"{First} {Last}".Trim();

    // Expression-bodied getter + setter
    private string _title = "";
    public string Title
    {
        get => _title;
        set => _title = value.Trim();
    }
}

required Properties (C# 11+)

Mark a property as required to force callers to set it in the object initializer.

C# Example Code
public class Product
{
    public required string Name  { get; init; }
    public required decimal Price { get; init; }
    public string? Description   { get; init; }
}

// Compile error if Name or Price is omitted:
var p = new Product { Name = "Laptop", Price = 999.99m };
Console.WriteLine($"{p.Name}: {p.Price:C}"); // Laptop: $999.99