Skip to main content
SEMastery
.NET Corebeginner

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.

12 min readUpdated November 27, 2025

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:

  • required is like the red star next to a box. You must fill it in.
  • init is 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.

How a value flows into a property and when it locks

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 later

Sometimes 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 here

The 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

Constructor
Object initializer
Frozen

Steps

1

Constructor

Inside the type's own constructor

2

Object initializer

The { ... } block when you call new

3

Frozen

Any later assignment is a compile error

After these, the value is frozen

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 styleSet at creation?Change later?Works with { ... } initializer?
get; set;YesYesYes
get; onlyOnly via constructorNoNo
get; init;YesNoYes

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 compiler's required checklist at object creation

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:

KeywordWhat it forcesWhen it acts
requiredA value must be suppliedAt creation time
initThe value cannot change after creationAfter creation
Both togetherMust supply it, then cannot edit itBoth moments

A picture of the full life of the object

Life of a required + init object

Write new
Provide required
Object built
Read only

Steps

1

Write new

Start the object initializer

2

Provide required

Compiler insists on every required value

3

Object built

All values are now in place

4

Read only

init members are frozen

Filled correctly, then locked for good

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:

Choosing between an object initializer and a SetsRequiredMembers constructor

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.

MistakeWhat happensFix
Using set when you meant initValue can be changed laterReplace set with init
Forgetting required on a must-have fieldObject builds with empty valueAdd required in front
Trying to assign an init property after creationCompile errorSet it inside the initializer or constructor
Adding [SetsRequiredMembers] but not setting all of themHidden empty values returnSet 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

Pick property
Must give?
Stay fixed?
Choose keywords

Steps

1

Pick property

Look at one property

2

Must give?

If yes, add required

3

Stay fixed?

If yes, use init not set

4

Choose keywords

required, init, or both

Two questions decide the keywords

And a final state view of what a finished object looks like over its lifetime:

The states of an init + required object

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.
  • init lets 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 + init make 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.
  • required checks that a value was given, not that it is sensible, so keep validating important inputs.
  • You need C# 9 for init and C# 11 for required; both are fully available on .NET 10 with C# 14.

References and further reading

Related Posts