C# Init Property: Immutable Object Initialization
c# init property: Learn how C# init-only properties enable immutable objects with object initializer syntax, and how they compare to set and readonly fields.
c# init property requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
C# init-only properties let you create immutable objects without the ceremony of constructor-only assignment. The init accessor is a variant of set that runs only during object initialization, so a property can be assigned in an object initializer or constructor but never changed afterward. This gives you the safety of immutability while keeping the convenience of object initializers.
The Problem That Init-Only Setters Solve
Before init existed, creating an immutable object in C# required one of two patterns: assign all properties in a constructor and use read-only properties (no setter at all), or use a private setter with a factory method. Both approaches work, but they force you to write more boilerplate and often make object initializers useless. With a read-only property, you cannot use an object initializer because there is no setter to call. With a private setter, you can use an object initializer but the property is still mutable from within the class, which can lead to accidental modifications.
The init accessor solves this by allowing assignment only during the initialization phase of the object's lifetime. After the constructor and any object initializers have finished running, the property becomes effectively read-only. This is enforced by the compiler, so you get immutability without giving up the expressive syntax of object initializers.
Declaring an Init-Only Property
Declaring an init-only property is almost identical to declaring a standard auto-property with a set accessor, except you use init instead:
public class Person { public string FirstName { get; init; } public string LastName { get; init; } }
You can then create an instance using an object initializer:
var person = new Person { FirstName = "Ada", LastName = "Lovelace" };
Once person is created, any attempt to assign to FirstName or LastName outside the initialization context produces a compile-time error. This is the core benefit: the compiler enforces immutability at the point of use, not just by convention.
You can also assign init-only properties from within a constructor, which is useful when you need to perform validation or derive values before the object is fully constructed:
public class Person { public Person(string firstName, string lastName) { FirstName = firstName ?? throw new ArgumentNullException(nameof(firstName)); LastName = lastName; } public string FirstName { get; init; } public string LastName { get; init; } }
In this example, the constructor is the only place that assigns the properties, but the init accessor still permits object initializer usage if you add a parameterless constructor. The compiler treats the constructor body as part of the initialization phase, so assignments there are allowed.
How Init Differs From Set and Readonly Fields
The distinction between init, set, and a read-only property (no setter) is important for understanding when to use each.
| Accessor | Assignment in constructor | Assignment in object initializer | Assignment after construction |
|---|---|---|---|
set | Allowed | Allowed | Allowed |
init | Allowed | Allowed | Not allowed |
| none | Allowed (via field) | Not allowed | Not allowed |
A property with no setter is typically backed by a readonly field. You can assign that field only in a constructor or in a field initializer. Object initializer syntax is not possible because there is no setter to call. In contrast, init provides a setter that the compiler restricts to the initialization phase, so you get the convenience of object initializers without sacrificing immutability.
A read-only property backed by a readonly field is the most restrictive and is appropriate when you want to guarantee that the value is set exactly once at construction time. Init properties are more flexible when you want to support object initializers or when you are working with serialization frameworks that require a parameterless constructor and property setters.
Using Init Properties in Object Initializers
Object initializers are a natural fit for init-only properties because they let you set multiple properties in a single expression. This is especially useful for DTOs, configuration objects, and test fixtures where you want to construct an object with a specific set of values without writing a verbose constructor.
Consider a simple configuration class:
public class AppConfig { public string ConnectionString { get; init; } public int TimeoutSeconds { get; init; } public bool EnableLogging { get; init; } }
You can create an instance with an object initializer:
var config = new AppConfig { ConnectionString = "Server=.;Database=app;Trusted_Connection=True;", TimeoutSeconds = 30, EnableLogging = true };
If AppConfig had only a constructor, you would need to write a constructor with three parameters and then call it with positional arguments. That is less readable and more error-prone when the number of properties grows. Init properties keep the object initializer syntax intact while preventing accidental reassignment later in the codebase.
One subtle point: object initializers are executed after the constructor. If your class has a constructor that sets a default value for an init-only property, the object initializer will override that default. This is by design and matches the behavior of a set accessor. The compiler treats the entire initialization sequence as the "init phase," so both constructor assignments and object initializer assignments are allowed.
When to Prefer Init Properties Over Readonly Fields
Choosing between an init property and a readonly field depends on how the object will be constructed and used.
Use an init property when:
- You want to support object initializers for readability.
- You are using serialization frameworks that require a parameterless constructor and writable properties.
- You want to avoid writing a constructor just to assign fields.
- You need to expose the value as a property for binding or reflection scenarios.
Use a readonly field when:
- The value is always known at construction time and never needs to be set from an object initializer.
- You want to minimize the public API surface; a readonly field can be exposed as a property with a getter only.
- You are working in a performance-sensitive context where a property accessor (even auto-property) might have a negligible but measurable cost. In practice, the JIT often inlines simple property getters, so the difference is usually insignificant.
For most application-level code, init properties are the better default because they are more flexible and still enforce immutability. Readonly fields are more appropriate for low-level types where you want to avoid any abstraction overhead or where the value is an implementation detail rather than part of the public contract.
Init Properties and Records
Records in C# use init-only properties extensively. When you declare a positional record, the compiler generates init-only properties for the parameters:
public record Person(string FirstName, string LastName);
This is equivalent to a class with init-only properties and a constructor. The with expression, which creates a copy of a record with modified properties, relies on the init accessor being available. For example:
var original = new Person("Ada", "Lovelace"); var modified = original with { FirstName = "Grace" };
The with expression works because the compiler generates a protected copy constructor and then uses the init accessors to apply the specified changes. If you define a record manually with init properties, you get the same behavior. This tight integration makes init properties a natural fit for immutable data models.
When you define a record with properties that are not part of the constructor, you can still use init-only setters to keep the record immutable:
public record Person { public string FirstName { get; init; } public string LastName { get; init; } public string FullName => $"{FirstName} {LastName}"; }
Here, FullName is a computed property, and the init-only setters ensure that FirstName and LastName cannot be changed after initialization. This is a common pattern for value objects and domain entities.
Limitations and Runtime Behavior
Init-only properties are enforced at compile time, so there is no runtime overhead compared to a standard set accessor. The compiler generates a normal setter method but restricts calls to it based on the calling context. Reflection can still invoke the setter at runtime, but that is true for any private or restricted member. If you are using a serialization library that sets properties via reflection, it will work because the underlying setter exists; the restriction is a compile-time rule, not a runtime guard.
One limitation is that you cannot use init-only properties in a class that requires a parameterless constructor and also needs to modify the property after construction in a factory method or a static method. For example, you cannot have a static method that creates an instance and then sets a property later; the compiler will reject the second assignment. This is intentional, but it means you sometimes need to use a builder pattern or a constructor with parameters to achieve the same effect.
Another edge case is inheritance. An init-only property in a base class can be set in a derived class's constructor or object initializer, but not after the object is constructed. This is consistent with the base class's immutability contract. If a derived class needs to modify a base class property after construction, that property should not be init-only.
Maintainability and Design Considerations
Init properties improve maintainability by making the immutability of an object explicit in its API. When a developer sees an init accessor, they immediately know that the property is set once during initialization and never changes. This reduces the mental load when reasoning about an object's state and prevents a whole class of bugs caused by accidental mutation.
However, init properties are not a silver bullet. They work best when the object is genuinely immutable after creation. If you find yourself wanting to modify an init property later, you should question whether the property should be mutable or whether the object should be replaced with a new instance. In many cases, using a with expression (for records) or a builder pattern is a better design than allowing mutation.
When designing a public API, consider whether consumers will need to create instances with object initializers. If so, init properties are a good choice. If you are building a library that exposes types with complex invariants, you might prefer constructors with validation and read-only properties to ensure that all required values are supplied at construction time. Init properties can be used in conjunction with constructors, but you lose the guarantee that all properties are set if you also provide a parameterless constructor. In that case, you should document which properties are required and which are optional.
Another maintainability aspect is that init properties work well with dependency injection and configuration binding. For example, ASP.NET Core's IOptions<T> pattern uses a class with properties that are bound from configuration. If those properties are init-only, the binding mechanism can still set them because it uses reflection, but you lose the ability to modify them in code after binding. This is often desirable because configuration should be read-only after startup.
Finally, consider the impact on serialization. Many serializers, including System.Text.Json, can set init-only properties because they call the setter via reflection. This means you can deserialize JSON directly into an immutable type without needing a custom converter. This is a significant advantage over read-only properties, which often require constructor-based deserialization or custom logic.
In summary, init properties are a practical tool for building immutable objects in C#. They combine the safety of immutability with the convenience of object initializers, and they integrate well with records, serialization, and configuration binding. The key is to use them where they fit the design, and to be aware of their compile-time restriction so you don't try to modify an init property after initialization.