0
0
C Sharp (C#)programming~15 mins

Init-only setters in C Sharp (C#) - Deep Dive

Choose your learning style9 modes available
Overview - Init-only setters
What is it?
Init-only setters are a feature in C# that allow you to set properties of an object only during its initialization. Once the object is created, these properties become read-only and cannot be changed. This helps create immutable objects while still allowing easy setup with object initializers. It uses the keyword 'init' instead of 'set' in property definitions.
Why it matters
Without init-only setters, making objects immutable in C# was either verbose or required custom constructors, making code harder to read and maintain. Init-only setters solve this by allowing properties to be set only once during creation, preventing accidental changes later. This leads to safer, more predictable code, especially in multi-threaded or complex applications.
Where it fits
Before learning init-only setters, you should understand basic C# properties and object initialization. After this, you can explore record types, immutability patterns, and advanced C# features like pattern matching and with-expressions that build on immutability concepts.
Mental Model
Core Idea
Init-only setters let you set a property once when creating an object, then lock it to prevent changes.
Think of it like...
It's like writing your name on a form with a pen that only works during the first minute; after that, the ink dries and you can't change it anymore.
Object Creation Flow:

  ┌───────────────┐
  │ Create Object │
  └──────┬────────┘
         │
         ▼
  ┌─────────────────────┐
  │ Set init-only props  │  <-- Allowed only here
  └─────────┬───────────┘
            │
            ▼
  ┌─────────────────────┐
  │ Object is immutable  │  <-- Properties locked
  └─────────────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding C# Properties
🤔
Concept: Learn what properties are and how getters and setters work in C#.
In C#, properties are like variables with special methods called getters and setters. A getter reads the value, and a setter changes it. For example: public class Person { public string Name { get; set; } } Here, Name can be read and changed anytime.
Result
You can create a Person and change Name whenever you want.
Knowing how properties work is essential because init-only setters change how setters behave.
2
FoundationObject Initialization Basics
🤔
Concept: Learn how to create objects and set properties using object initializers.
You can create an object and set its properties in one step: var person = new Person { Name = "Alice" }; This sets Name right when the object is created.
Result
The person object has Name set to "Alice" immediately after creation.
Object initializers make code cleaner and are the moment when init-only setters allow property setting.
3
IntermediateIntroducing Init-only Setters
🤔Before reading on: do you think init-only setters allow changing properties after object creation? Commit to yes or no.
Concept: Init-only setters let you set properties only during object creation, then make them read-only.
Instead of 'set', use 'init' in property definitions: public class Person { public string Name { get; init; } } Now, you can set Name only when creating the object: var person = new Person { Name = "Alice" }; Trying to change person.Name later causes a compile error.
Result
Properties with init-only setters can be set during initialization but not modified later.
Understanding that 'init' restricts property changes after creation helps prevent bugs from accidental modifications.
4
IntermediateUsing Init-only Setters with Records
🤔Before reading on: do you think records and init-only setters are unrelated? Commit to yes or no.
Concept: Records in C# use init-only setters by default to support immutability and easy copying.
Records are special classes designed for immutable data: public record Person { public string Name { get; init; } } You can create and copy records with new values: var p1 = new Person { Name = "Alice" }; var p2 = p1 with { Name = "Bob" }; Init-only setters make this pattern safe and simple.
Result
Records use init-only setters to allow safe, immutable data structures with easy copying.
Knowing how init-only setters power records reveals why immutability is easier in modern C#.
5
IntermediateInit-only Setters and Constructors
🤔
Concept: Init-only setters can be combined with constructors for flexible object creation.
You can set init-only properties inside constructors: public class Person { public string Name { get; init; } public Person(string name) { Name = name; // Allowed } } This lets you enforce required properties while keeping immutability.
Result
Init-only properties can be set in constructors or object initializers but not changed later.
Understanding this flexibility helps design safer, clearer APIs.
6
AdvancedLimitations and Compiler Enforcement
🤔Before reading on: do you think init-only setters are enforced at runtime or compile time? Commit to your answer.
Concept: Init-only setters are enforced by the compiler, not at runtime, ensuring properties can't be changed after initialization in normal code.
The compiler generates code that allows setting init-only properties only during object creation or inside constructors. Trying to set them later causes a compile error. However, reflection or unsafe code can bypass this, but that is discouraged. Example: person.Name = "Charlie"; // Compile error But reflection can still change it, which breaks immutability.
Result
Init-only setters provide compile-time safety but not absolute runtime immutability.
Knowing compiler enforcement boundaries helps understand when immutability can be trusted.
7
ExpertInit-only Setters Internals and IL Code
🤔Before reading on: do you think init-only setters generate different IL code than normal setters? Commit to yes or no.
Concept: Init-only setters generate special Intermediate Language (IL) code with a hidden 'modreq' that restricts setting outside initialization contexts.
Under the hood, init-only setters look like normal setters but have a special modifier in IL called 'modreq(System.Runtime.CompilerServices.IsExternalInit)'. This tells the compiler and runtime that the setter can only be called during object initialization. This is why older runtimes don't support init-only setters without extra support. This design allows backward compatibility and smooth adoption.
Result
Init-only setters are a compiler and runtime cooperation feature, not just syntax sugar.
Understanding IL details explains why init-only setters require modern runtimes and how they enforce immutability.
Under the Hood
Init-only setters work by marking the setter method with a special modifier in the compiled code that restricts calls to the setter outside of object initialization contexts. The C# compiler enforces this by allowing property assignment only in constructors or object initializers. At runtime, the setter exists but is protected by this metadata, preventing normal code from calling it after initialization.
Why designed this way?
This design was chosen to balance immutability with ease of use. Before init-only setters, immutable objects required verbose constructors or private setters. The new approach allows simple, readable object initialization syntax while preventing accidental changes later. Using compiler enforcement and IL modifiers avoids runtime overhead and keeps backward compatibility with existing code and runtimes.
┌─────────────────────────────┐
│ C# Source Code              │
│ public string Name { get; init; } │
└──────────────┬──────────────┘
               │
               ▼
┌─────────────────────────────┐
│ Compiler                    │
│ - Generates getter/setter   │
│ - Marks setter with modreq  │
│   IsExternalInit            │
└──────────────┬──────────────┘
               │
               ▼
┌─────────────────────────────┐
│ IL Code                    │
│ - Setter method with modreq │
│ - Runtime enforces usage    │
└──────────────┬──────────────┘
               │
               ▼
┌─────────────────────────────┐
│ Runtime                    │
│ - Allows setter calls only  │
│   during initialization    │
│ - Compile-time errors if   │
│   used later               │
└─────────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Can you change an init-only property after object creation using normal code? Commit to yes or no.
Common Belief:Init-only setters are just like normal setters and can be changed anytime.
Tap to reveal reality
Reality:Init-only setters can only be set during object creation or inside constructors; after that, they are read-only and cannot be changed by normal code.
Why it matters:Believing you can change init-only properties later leads to bugs where you expect immutability but accidentally modify objects.
Quick: Do init-only setters guarantee absolute immutability at runtime? Commit to yes or no.
Common Belief:Init-only setters make objects completely immutable at runtime.
Tap to reveal reality
Reality:Init-only setters enforce immutability only at compile time; reflection or unsafe code can still modify properties at runtime.
Why it matters:Assuming absolute immutability can cause security or consistency issues if reflection is used carelessly.
Quick: Are init-only setters a runtime feature or compiler feature? Commit to your answer.
Common Belief:Init-only setters are enforced by the runtime environment only.
Tap to reveal reality
Reality:Init-only setters are primarily enforced by the C# compiler through special IL metadata; the runtime cooperates but enforcement is compile-time focused.
Why it matters:Misunderstanding enforcement can cause confusion about compatibility and debugging.
Quick: Can you use init-only setters in older C# versions before 9.0? Commit to yes or no.
Common Belief:Init-only setters can be used in any C# version as they are just syntax sugar.
Tap to reveal reality
Reality:Init-only setters were introduced in C# 9.0 and require modern runtimes; older versions do not support them.
Why it matters:Trying to use init-only setters in older projects causes compile errors and confusion.
Expert Zone
1
Init-only setters allow setting properties inside constructors, which means immutability can be combined with validation logic during object creation.
2
The 'modreq' IL modifier used for init-only setters is a subtle mechanism that enables backward compatibility by making the setter method appear normal to older runtimes but restricted to newer compilers.
3
Init-only setters do not prevent mutation through mutable reference types assigned to them; immutability applies only to the property reference, not the object's internal state.
When NOT to use
Avoid init-only setters when you need properties to change after object creation or when targeting older C# versions or runtimes that do not support this feature. Instead, use traditional setters or readonly fields with constructor initialization.
Production Patterns
Init-only setters are widely used in data transfer objects (DTOs), configuration classes, and record types to enforce immutability while keeping initialization simple. They are common in APIs that require thread-safe or predictable data models.
Connections
Immutable Data Structures
Init-only setters enable creating immutable objects, a core idea in immutable data structures.
Understanding init-only setters helps grasp how immutability is enforced in programming, which improves reliability and concurrency.
Functional Programming
Init-only setters support immutability, a key principle in functional programming.
Knowing how C# supports immutability bridges imperative and functional programming styles, enhancing code safety.
Legal Contracts
Like legal contracts that can be signed only once, init-only setters allow setting values only once during creation.
This cross-domain connection shows how rules that limit changes after agreement improve trust and predictability.
Common Pitfalls
#1Trying to change an init-only property after object creation.
Wrong approach:person.Name = "Bob"; // Error: Cannot assign to 'Name' because it is init-only
Correct approach:var person = new Person { Name = "Bob" }; // Set during initialization
Root cause:Misunderstanding that init-only properties are read-only after initialization.
#2Assuming init-only setters make the entire object deeply immutable.
Wrong approach:public class Person { public List Tags { get; init; } } var p = new Person { Tags = new List() }; p.Tags.Add("friend"); // Allowed, mutates internal list
Correct approach:Use immutable collections or make internal state readonly: public class Person { public IReadOnlyList Tags { get; init; } } var p = new Person { Tags = new List().AsReadOnly() };
Root cause:Confusing property immutability with deep immutability of referenced objects.
#3Using init-only setters in projects targeting older C# versions.
Wrong approach:public string Name { get; init; } // Compile error in C# 8 or earlier
Correct approach:Use readonly properties with constructor initialization: public string Name { get; } public Person(string name) { Name = name; }
Root cause:Not knowing the language version requirements for init-only setters.
Key Takeaways
Init-only setters let you set properties only during object creation, making objects immutable afterward.
They improve code safety by preventing accidental changes to important data after initialization.
Init-only setters are enforced by the compiler using special metadata, not by runtime checks alone.
They work well with records and immutable patterns, simplifying modern C# programming.
Understanding their limits, like shallow immutability and version requirements, is key to using them effectively.