2. Choosing the property modifiers
Property setters in C# play a crucial role in defining how you can interact with class and record members. The set, init, and get-only accessors offer different levels of mutability and initialization control, crucial for both mutable and immutable object design. There are a couple of options out there, which one should you choose?
| Optional | Required |
|---|---|
public string Name { get; } |
public required string Name { get; } |
public string Name { get; set; } |
public required string Name { get; set; } |
public string Name { get; init; } |
public required string Name { get; init; } |
Mutable Properties with set
The set accessor allows properties to be changed at any point in an object’s lifetime. It’s commonly used in classes where mutability is a requirement.
public class Person
{
public string Name { get; set; }
}
// Initialization and mutation
var person = new Person { Name = "Alice" };
person.Name = "Bob"; // Property can be changed after initialization
However, records also support this but is generally considered an anti-pattern.
public record Person
{
public string Name { get; set; }
}
var mutablePerson = new MutablePerson { Name = "Alice" };
mutablePerson.Name = "Bob"; // The Name property can be changed after initialization
Immutable Properties with init
The init accessor, introduced in C# 9.0, is designed for scenarios where you want to allow property values to be set at the time of object creation but remain immutable afterward.
public [class|record] Person
{
public string Name { get; init; }
}
// Initialization
var immutablePerson = new Person { Name = "Alice" };
// immutablePerson.Name = "Bob"; // This line would result in a compile-time error
Read-Only Properties with get
Defining a property with only a get accessor makes it read-only. This is useful for both computed properties and ensuring that a property remains unchanged after the object’s construction.
public [class|record] Person
{
public string Name { get; }
public Person(string name)
{
Name = name;
}
}
// Initialization
var readOnlyPerson = new PersonWithReadOnlyProperty("Alice");
// readOnlyPerson.Name = "Bob"; // Not allowed
Required Properties with required
The required keyword ensures that certain properties must be initialized during object creation, enhancing compile-time checks. It’s applicable to both classes and records.
public [class|record] Person
{
public required string Name { get; init; }
}
// Initialization
var requiredPerson = new Person { Name = "Alice" };
// This attempt will fail to compile
var personWithoutName = new Person(); // Compile-time error
Conclusion
flowchart TD
A[Mutable after initialization?]
A -- Yes --> B[get set]
A -- No --> C[Value assigned or calculated in the constructor?]
C -- Yes --> D[get]
C -- No --> E[get init]
D --> F[Value required?]
E --> F
F -- Yes --> G[Add required]
- Mutable vs. Immutable: Use
setfor mutable properties in classes. Opt forinitin records (and classes when appropriate) for immutable properties. - Initialization Control:
initallows properties to be set at initialization time, perfect for immutable data patterns.requiredensures all necessary properties are initialized. - Read-Only Properties: Use
get-only for properties that should not change after an object is constructed, suitable for computed properties or fixed values.