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.
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, 30Auto Property Initializers
Set a default value without a constructor.
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); // ProductionRead-Only Properties (getter only)
A property with only a getter can only be set in the constructor or with an initializer.
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 errorinit-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.
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 constructionAsymmetric Access Modifiers
Give the getter a wider access level than the setter.
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 privateProperties 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.
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); // 212Computed Properties
A computed property derives its value from other data. It has only a getter and no backing field.
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: 16Property vs Field
| Property | Field | |
|---|---|---|
| Syntax | public int Age { get; set; } | public int Age; |
| Validation logic | Yes | No |
| Can be in an interface | Yes | No |
| Supports data binding | Yes | No |
| Breaking change if added later | No | Yes (changes binary) |
| Recommended for public API | Yes | No |
Fields are an implementation detail — keep them private. Expose data via properties.
// 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.
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.
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