C# Init Properties
Introduction
C# 9.0 introduced a new keyword called init
that provides a more flexible way to create immutable objects. Init-only properties allow you to set property values during object initialization but prevent them from being changed afterward. This creates a perfect balance between the flexibility of regular properties and the immutability of readonly fields.
In this tutorial, you'll learn:
- What init properties are and why they're useful
- How to implement init properties
- When to use init properties instead of regular setters or readonly fields
- Practical patterns and use cases
Understanding Init Properties
What Are Init Properties?
Init properties are a special type of property that can only be set during object initialization. After the object is constructed, init properties become read-only, effectively making the object (or parts of it) immutable.
Here's the basic syntax:
public class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
public int Age { get; init; }
}
In the code above, the properties can only be set during initialization but cannot be modified afterward.
Regular Properties vs Init Properties vs Readonly Fields
Let's compare different ways of handling data in C# classes:
public class ComparisonExample
{
// Regular property - can be read and modified anytime
public string RegularProperty { get; set; }
// Init property - can only be set during initialization
public string InitProperty { get; init; }
// Readonly field - can only be set in constructor
public readonly string ReadonlyField;
public ComparisonExample(string readonlyValue)
{
ReadonlyField = readonlyValue;
}
}
Using Init Properties
Object Initialization
Init properties work seamlessly with object initializers:
// Creating an object with init properties
var person = new Person
{
FirstName = "John",
LastName = "Doe",
Age = 30
};
// This works during initialization
But attempting to modify them after initialization will cause a compilation error:
// This will cause a compilation error
// person.FirstName = "Jane"; // Error CS8852: Init-only property or indexer 'Person.FirstName' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.
Working with Constructors
Init properties can also be set within constructors:
public class Employee
{
public string Name { get; init; }
public int Id { get; init; }
public string Department { get; init; }
public Employee(string name, int id)
{
Name = name;
Id = id;
}
}
// Usage
var employee = new Employee("Alice Smith", 12345)
{
Department = "Engineering"
};
Practical Examples
Creating Immutable DTOs (Data Transfer Objects)
Init properties are perfect for DTOs that shouldn't change after creation:
public class CustomerDTO
{
public int Id { get; init; }
public string Name { get; init; }
public string Email { get; init; }
public DateTime RegisteredDate { get; init; }
}
// Usage
var customer = new CustomerDTO
{
Id = 101,
Name = "Sarah Johnson",
Email = "[email protected]",
RegisteredDate = DateTime.Now
};
Implementing Value Objects
Value objects in Domain-Driven Design benefit from immutability:
public class Address
{
public string Street { get; init; }
public string City { get; init; }
public string State { get; init; }
public string ZipCode { get; init; }
public override bool Equals(object obj)
{
if (obj is not Address other) return false;
return Street == other.Street &&
City == other.City &&
State == other.State &&
ZipCode == other.ZipCode;
}
public override int GetHashCode()
{
return HashCode.Combine(Street, City, State, ZipCode);
}
}
Configuration Settings
Init properties are ideal for configuration objects that shouldn't change after initialization:
public class ApplicationSettings
{
public string ApiUrl { get; init; }
public int Timeout { get; init; }
public bool EnableLogging { get; init; }
public LogLevel DefaultLogLevel { get; init; }
}
// Usage
var settings = new ApplicationSettings
{
ApiUrl = "https://api.example.com",
Timeout = 30,
EnableLogging = true,
DefaultLogLevel = LogLevel.Information
};
Advanced Usage: Records and Init Properties
C# 9.0 also introduced records, which combine nicely with init properties for truly immutable data:
public record Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
public int Age { get; init; }
}
// Usage
var person1 = new Person
{
FirstName = "John",
LastName = "Doe",
Age = 30
};
// With records, you can use non-destructive mutation via the 'with' keyword
var person2 = person1 with { Age = 31 };
// person1 is still 30, person2 is a new instance with Age = 31
Common Use Cases for Init Properties
- Domain models that need to enforce invariants and prevent unexpected changes
- DTOs for API responses and data serialization
- Configuration objects that should remain consistent once initialized
- Value objects in Domain-Driven Design
- Command objects in Command Query Responsibility Segregation (CQRS) patterns
Summary
Init properties provide a powerful way to create immutable objects while maintaining the flexibility of object initializers. They offer several advantages:
- They allow for more flexible construction compared to readonly fields
- They provide immutability after initialization
- They work seamlessly with object initializers and records
- They help prevent bugs caused by unexpected state changes
By using init properties, you can write safer, more predictable code, especially in multi-threaded environments where immutability is a key strategy for preventing race conditions.
Exercises
- Create an immutable
Book
class with properties for Title, Author, ISBN, and PublicationYear using init properties. - Design a
ShoppingCartItem
record with init properties for ProductId, ProductName, Quantity, and UnitPrice. - Create a configuration class for a web application with init properties for database connection string, cache timeouts, and feature flags.
Additional Resources
- Microsoft Docs: Init-only properties
- C# 9.0 on the record
- Immutability in C#: Patterns and Practices
Happy coding with init properties!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)