C# init-only and required Properties: A Beginner's Guide
Learn C# init-only and required properties with simple analogies, diagrams, and code. Build safe, immutable objects that are filled correctly every time.
C# init-only and required Properties: A Beginner's Guide
Imagine you fill out a form to open a bank account. The bank asks for your name and your date of birth. These two things are required — you cannot submit the form with them blank. And once the account is open, your date of birth is locked. You cannot change it later, because you cannot change the day you were born.
C# has two small keywords that work exactly like this form:
requiredis like the red star next to a box. You must fill it in.initis like the lock that clicks shut after you submit. The value is set once and then frozen.
This article will teach you both, step by step, in plain words. By the end you will be able to build objects that are filled correctly every single time and can never be quietly broken later.
A quick look at where we are going
Before the deep dive, here is the big picture of how a value gets into a property and what each keyword controls.
First, a tiny refresher on properties
A property is a safe doorway to a value inside an object. You usually see a get (read) part and a set (write) part.
public class Student
{
// get lets you read the value
// set lets you change the value, any time you like
public string Name { get; set; }
public int Age { get; set; }
}With this normal property, anyone can change Name whenever they want:
var s = new Student { Name = "Aarav", Age = 12 };
s.Name = "Someone else"; // allowed, even much laterSometimes that is fine. But often it is not. A student's roll number should not change after they enrol. A bank transaction amount should not change after the payment is made. We want the value to be set once and then stay put. That is the problem init solves.
The init keyword: set once, then freeze
The init keyword replaces set. It says: "You may give me a value, but only while the object is being created. After that, the door is sealed."
public class Student
{
public string Name { get; init; } // can only be set at creation
public int RollNumber { get; init; } // can only be set at creation
}Now look at what works and what does not:
// This works. We are still creating the object.
var s = new Student { Name = "Aarav", RollNumber = 42 };
// This FAILS to compile. The object already exists.
s.RollNumber = 99; // error: init-only property cannot be assigned hereThe compiler stops you. This is wonderful, because a mistake that would have been a hidden bug now becomes a loud red error before your program even runs.
When exactly can an init property be set?
There are only two moments. Think of them as the "construction site." Once the building is open to the public, no more changes.
The two moments you can set an init property
Steps
Constructor
Inside the type's own constructor
Object initializer
The { ... } block when you call new
Frozen
Any later assignment is a compile error
Here is the same idea in code, showing a constructor doing the work:
public class Student
{
public string Name { get; init; }
public int RollNumber { get; init; }
public Student(string name, int rollNumber)
{
Name = name; // allowed: inside the constructor
RollNumber = rollNumber; // allowed: inside the constructor
}
}Why not just use a read-only property?
You might know about get-only properties (no set at all). They are also unchangeable. So why do we need init?
The difference is object initializers. A pure read-only property usually forces you to write a constructor and pass every value through it. With init, you keep the clean, friendly { Name = ..., Age = ... } style and you get immutability. You get the best of both.
| Property style | Set at creation? | Change later? | Works with { ... } initializer? |
|---|---|---|---|
get; set; | Yes | Yes | Yes |
get; only | Only via constructor | No | No |
get; init; | Yes | No | Yes |
The required keyword: you must fill this in
init solves "do not change me later." But it does not force anyone to give a value in the first place. Look at this:
public class Student
{
public string Name { get; init; }
}
// Nothing is forcing us to set Name. This compiles fine:
var s = new Student();
// s.Name is now null. Oops.A blank Name is a bug waiting to happen. This is where required (added in C# 11) steps in. Put required in front of a property and the caller must give it a value, or the code will not compile.
public class Student
{
public required string Name { get; init; }
public int Age { get; init; }
}
// This FAILS to compile: Name was not set.
var bad = new Student { Age = 12 };
// This works: Name is provided.
var good = new Student { Name = "Aarav", Age = 12 };The error happens at build time, not when the program is running and a user is watching. That is a huge win. Bugs caught early are cheap. Bugs caught in production are expensive and stressful.
How the compiler checks required
The compiler keeps a little checklist of every required member. When it sees new Student { ... }, it ticks off each one that you set. If any box is still empty, it refuses to build.
The dream team: required + init together
Now combine them. This is the pattern you will use again and again.
public class BankAccount
{
public required string Owner { get; init; }
public required string AccountNumber { get; init; }
public decimal Balance { get; init; } // optional, can stay 0
}Read this out loud: the owner and the account number must be given (because of required), and none of these three can ever be changed afterwards (because of init). That is a rock-solid, trustworthy object.
// Must provide Owner and AccountNumber, or it will not compile.
var account = new BankAccount
{
Owner = "Priya",
AccountNumber = "SB-100245",
Balance = 5000m
};
// Frozen forever. This line is a compile error:
// account.Balance = 999999m;Here is how each keyword carries its own job:
| Keyword | What it forces | When it acts |
|---|---|---|
required | A value must be supplied | At creation time |
init | The value cannot change after creation | After creation |
| Both together | Must supply it, then cannot edit it | Both moments |
A picture of the full life of the object
Life of a required + init object
Steps
Write new
Start the object initializer
Provide required
Compiler insists on every required value
Object built
All values are now in place
Read only
init members are frozen
A real example you can relate to
Let's model a movie ticket. A ticket without a seat or a show time makes no sense, so those are required. And a printed ticket should never change its details, so everything is init.
public class MovieTicket
{
public required string MovieName { get; init; }
public required string SeatNumber { get; init; }
public required DateTime ShowTime { get; init; }
public decimal Price { get; init; }
}
var ticket = new MovieTicket
{
MovieName = "Sky Riders",
SeatNumber = "H12",
ShowTime = new DateTime(2026, 6, 14, 18, 30, 0),
Price = 250m
};If a teammate later writes new MovieTicket { Price = 250m } and forgets the seat and show time, the build breaks immediately. Nobody can hand out a broken ticket by accident.
A helpful escape hatch: SetsRequiredMembers
Sometimes you want a constructor that already sets all the required values. In that case, callers using that constructor should not be forced to repeat them in an object initializer. You tell the compiler "trust me, this constructor fills everything" with the SetsRequiredMembers attribute.
using System.Diagnostics.CodeAnalysis;
public class MovieTicket
{
public required string MovieName { get; init; }
public required string SeatNumber { get; init; }
[SetsRequiredMembers]
public MovieTicket(string movieName, string seatNumber)
{
MovieName = movieName;
SeatNumber = seatNumber;
}
}
// Because of SetsRequiredMembers, this is allowed without an initializer:
var t = new MovieTicket("Sky Riders", "H12");Use this carefully. The attribute switches off the safety check for that constructor, so it is now your job to make sure the constructor really does set every required member. A handy diagram of when to reach for it:
Records love these keywords too
If you have met records (a short way to write data classes), init and required fit right in. Records already make immutable data feel natural, and required adds the "must fill" guarantee.
public record Customer
{
public required string Name { get; init; }
public required string Email { get; init; }
public string? Phone { get; init; } // optional
}
var c = new Customer { Name = "Rohan", Email = "[email protected]" };This is a very common shape for the small data objects you pass around in real apps, such as the model behind a sign-up form.
Common beginner mistakes (and easy fixes)
A few traps catch newcomers. Here they are, with the cure.
| Mistake | What happens | Fix |
|---|---|---|
Using set when you meant init | Value can be changed later | Replace set with init |
Forgetting required on a must-have field | Object builds with empty value | Add required in front |
Trying to assign an init property after creation | Compile error | Set it inside the initializer or constructor |
Adding [SetsRequiredMembers] but not setting all of them | Hidden empty values return | Set every required member in that constructor |
One more gentle warning. required checks that a value was supplied, not that the value is "good." Someone can still pass an empty string "". So for real apps you may still want extra validation on top. required removes the "I forgot" bugs, which is already a big help.
Putting it all together: one clear flow
Here is the whole decision in one place. When you design a new data type, ask two simple questions for each property: "Must it be given?" and "Should it stay fixed?"
Designing a property
Steps
Pick property
Look at one property
Must give?
If yes, add required
Stay fixed?
If yes, use init not set
Choose keywords
required, init, or both
And a final state view of what a finished object looks like over its lifetime:
Which versions do you need?
- init-only properties: C# 9 and later.
- required members: C# 11 and later.
Both are fully supported in modern .NET. The current Long-Term Support release is .NET 10, and the latest language version is C# 14, so you are in safe territory on any up-to-date project. If you are on an older target, check that your <LangVersion> is high enough.
Quick recap
- A normal
get; set;property can be changed at any time, which is sometimes risky. initlets a property be set only during creation (constructor or object initializer), then freezes it. This gives you immutability while keeping the clean{ ... }syntax.required(C# 11) forces the caller to supply a value at creation time, checked at compile time so mistakes never reach production.- Together,
required+initmake a property that must be filled and can never be edited again — perfect for things like IDs, account numbers, and ticket details. [SetsRequiredMembers]lets a constructor that already fills everything turn off the required check — use it carefully.- These keywords work great with records for small immutable data objects.
requiredchecks that a value was given, not that it is sensible, so keep validating important inputs.- You need C# 9 for
initand C# 11 forrequired; both are fully available on .NET 10 with C# 14.
References and further reading
- The init keyword - C# reference (Microsoft Learn)
- required modifier - C# reference (Microsoft Learn)
- Object and collection initializers (Microsoft Learn)
- Properties (Microsoft Learn)
- C# Init Only and Required Properties (Anton Martyniuk)
- Introducing C# 11: Required properties (Anthony Giretti)
Related Posts
How to Apply Functional Programming in C#: A Beginner's Guide
Learn functional programming in C# the simple way: pure functions, immutability, records, LINQ, pattern matching, and composition with friendly examples.
Getting Started with C# Records: A Beginner's Friendly Guide
Learn C# records the easy way: value equality, with expressions, positional syntax, and record struct, explained with simple real-life examples.
How to Write Elegant Code with C# Pattern Matching
Learn C# pattern matching the easy way. Use is, switch expressions, property and list patterns to write clean, readable, and elegant .NET code.
New Features in C# 13: A Friendly Beginner's Guide
Learn the new features in C# 13 with simple words, real-life examples, diagrams, and code you can read in minutes. Great for beginners.
Mastering Exception Handling in C#: A Comprehensive Guide
Learn C# exception handling the friendly way: try, catch, finally, custom exceptions, filters, throw vs throw ex, and real best practices for .NET 10.
Why I Switched to Primary Constructors for DI in C#
A friendly guide on why primary constructors made my C# dependency injection cleaner, with simple examples, diagrams, tables, and honest trade-offs.