Back to Blog
C#

C# Init Keyword: Immutable Properties After Construction

Learn how to use the C# init keyword to create immutable properties that still support object initializers. Understand syntax, constraints, and practical examples.

C#object initializersimmutabilityproperty initializationreadonly
A C# init keyword concept showing a locked property after initialization.

The C# init keyword defines an accessor that allows a property to be assigned only during object initialization. Unlike a set accessor, an init accessor cannot be called after construction completes. This gives you the convenience of object initializers without sacrificing immutability.

Consider a typical mutable property:

public class Person { public string Name { get; set; } }

With the set accessor, Name can be changed at any time. If you want to prevent changes after the object is created, you might have used a readonly field assigned in a constructor. The init accessor offers a middle ground: assign during construction, then freeze.

Init Accessor Syntax

Use init in place of set when declaring a property:

public class Person { public string Name { get; init; } public int Age { get; init; } }

Now you can create an instance with an object initializer:

var person = new Person { Name = "Alice", Age = 30 };

After the initializer completes, Name and Age cannot be reassigned:

person.Name = "Bob"; // Compiler error: Init-only property can only be assigned in an object initializer or 'this' constructor

This behavior is enforced at compile time. Any attempt to modify an init-only property outside construction results in a compilation error.

How Init Differs from Set and Readonly

The set accessor allows assignment at any time. readonly fields can only be assigned in a constructor or field initializer. The init accessor sits between them: it permits assignment from within a constructor, from an object initializer, or from a with expression (for records), but not after construction.

Here is a constructor that assigns an init-only property:

public class Person { public string Name { get; init; } public Person(string name) { Name = name; } }

This is valid because the assignment happens during construction. After initialization finishes, no further assignment is allowed.

How the Compiler Enforces Init

The compiler emits an init accessor as a setter that carries a metadata modifier referencing System.Runtime.CompilerServices.IsExternalInit. The C# compiler uses that modifier to reject calls outside initialization contexts. The exact IL can vary, but the result is the same: direct assignment outside those contexts fails at compile time. The runtime does not enforce this restriction, so reflection can still call the setter.

Practical Use Cases for Init-Only Properties

Use init when you want to create immutable data that is easy to construct with object initializers. Typical scenarios include:

  • Value objects that represent a snapshot of data.
  • Configuration objects that are read after creation.
  • Data transfer objects (DTOs) that should not change after being populated.
  • Entities in a domain model where identity and core attributes are fixed after creation.

For example, a configuration class:

public class AppConfig { public string ConnectionString { get; init; } public int TimeoutSeconds { get; init; } }

The app loads configuration during startup and passes it to services. Those services should not modify it.

Working with with Expressions in Records

Records support init-only properties and the with expression. The with expression creates a copy of the record with specified properties changed, but it works even with init accessors because the copy is still considered to be in construction.

public record Person { public string Name { get; init; } public int Age { get; init; } } var alice = new Person { Name = "Alice", Age = 30 }; var olderAlice = alice with { Age = 31 };

The original alice remains unchanged. olderAlice is a new instance. This maintains immutability while allowing a form of "update."

Limitations and Constraints

Init-only properties have a few limitations and caveats you should understand.

Interfaces and Init-Only Properties

Interfaces can declare init-only properties. A class that implements the interface must also declare the property with init.

public interface IHasName { string Name { get; init; } } public class Person : IHasName { public string Name { get; init; } }

Reflection Can Still Modify the Property

The compiler blocks direct assignment, but reflection can technically set an init-only property after construction. This is not recommended, but immutability here is a compile-time contract, not a runtime security guarantee.

Comparing Init to Alternatives

ApproachAssign in ConstructorAssign After ConstructionObject Initializer Support
set accessorYesYesYes
init accessorYesNoYes
readonly fieldYesNoNo

A readonly field is the strongest immutability guarantee, but it reduces flexibility. Init-only properties provide a balance.

Making the Right Choice

Use init when you need the simplicity of object initializers but want properties to be immutable after construction. Prefer readonly fields when you are certain the value will never change and you are willing to forgo object initializer syntax. Use set only when the property genuinely needs to be mutable throughout the object's lifetime.

For example, in a DTO that is populated from a JSON payload and then passed to business logic, init prevents accidental corruption. If you are building an entity whose identity never changes, init on the identity property (such as an Id) is appropriate.

Compatibility and Language Version

The init keyword was introduced in C# 9. The compiler needs to use a C# 9 language version, and the generated setter references the IsExternalInit type. .NET 5 and later include that type; for older target frameworks, you can add the IsExternalInit polyfill and set <LangVersion>9.0</LangVersion> (or a later version) in the project file.

<PropertyGroup> <LangVersion>9.0</LangVersion> </PropertyGroup>

For most modern projects, you do not need to define IsExternalInit yourself.

Common Pitfall: Assigning From a Constructor Helper

One common mistake is trying to initialize an init-only property from a method called by the constructor. Assignments in a method called from the constructor are not within the construction context, so they are not allowed.

public class Person { public string Name { get; init; } public Person() { SetName(); } private void SetName() { Name = "Default"; // Error: cannot assign to init-only property } }

If you need to set init-only properties from helper methods, move that logic into the constructor body directly, or make the helper return the value and assign it in the constructor.

public string Name { get; init; } public Person() { Name = GetDefaultName(); } private string GetDefaultName() => "Default";

Realistic Example: A Value Object

Here is a complete value object using init-only properties:

public class Money { public decimal Amount { get; init; } public string Currency { get; init; } }

You can create a Money instance with a clear initializer:

var price = new Money { Amount = 19.99m, Currency = "USD" };

Because Money is immutable, you can safely share instances across threads without locks. This is a practical benefit in concurrent code where objects are passed between tasks.

Performance in High-Frequency Code

Init-only properties add no runtime overhead compared with ordinary setter-based properties. The accessor compiles to similar IL; the difference is a metadata marker used for compile-time enforcement. In high-frequency scenarios, using init-only properties does not degrade runtime performance.

Maintainability Considerations

Init-only properties make the intention of the code clearer. A property that is init-only tells future maintainers that it should not be changed after creation. This reduces the chance of accidental mutation bugs. It also enables safer concurrency because immutable data can be shared without synchronization.

When Not to Use Init

If you need lazy loading or want a property to be changed as part of business logic, init is not appropriate. For instance, an entity's Status property might need to change from Pending to Approved during its lifetime. Using init would prevent such transitions, so a set accessor with additional logic is better.

Similarly, if you rely on serializers that call property setters during deserialization, init-only properties can be problematic. Some serialization frameworks require a parameterless constructor and settable properties. In those cases, either use set or configure the serializer to support init.

Advanced Scenario: Chained Construction

Init-only properties can be assigned from within a constructor, and a derived class constructor can assign an inherited init-only property.

public class Base { public string Id { get; init; } } public class Derived : Base { public Derived(string id) { Id = id; // allowed: constructors can access init accessors } }

This is useful when you need derived classes to set base properties during construction while keeping them immutable afterward.

Conclusion

The init keyword is a precise tool for creating immutable properties that support convenient initialization. Use it when you want to prevent post-construction changes but also want the clarity of object initializers. The compiler enforces that contract in ordinary C# code, providing a strong basis for maintainability and concurrency. By understanding its limitations and correct usage, you can apply init where it truly belongs.

C# init keyword: Practical Usage and Code Examples | RYUSLOG DEV