C# 9.0 Init-Only Properties

Marshall Todt
Hitachi Solutions Braintrust
3 min readNov 13, 2020

--

If there’s one thing this year’s DotNetConf has taught me, it is that I don’t even realize what I want improved. This is very much a “flip the script” moment, as I’m used to being on the opposite side of that conversation, trying to deliver improvements to a customer who may or may not realize where their workflows need the most improvement. Init-Only Properties are definitely something I never would have asked for, but now I am left wondering why I tolerated doing things differently.

What Are Init-Only Properties?

C# 9.0 adds a new keyword to autogenerated properties called init. When the init keyword is used, it restricts a property to only being set by a Constructor or during Nested Object Creation. After the object is created, the property becomes immutable. Here’s the example the DotNet Team provided:

Assuming you have this class:

public class Person
{
public string? FirstName { get; init; }
public string? LastName { get; init; }
}

You can initialize these fields the way you might expect normally:

var person = new Person{ FirstName = "Mads", LastName = "Nielsen" };
// Or if you have a constructor
person = new Person("Mads", "Nielson");

However, if you try to change one of the properties using an init keyword:

person.LastName = "Torgersen"; // ERROR!

You’d get an error (during the demo this was a compiler error too like most accessor errors, so no worry about code failing at runtime due to this).

Why Is This Important?

Prior to C# 9.0, there were two real ways to handle properties you wanted to make immutable. The first was to use a private setter, and populate the value in the constructor like this:

public Person(string firstName, string lastName)
{
FirstName = firstname;
LastName = lastName;
}
public string FirstName {get; private set; }
public string LastName {get; private set; }

This had two issues: First, it would not allow you to use Nested Object Creation, forcing you to use the constructor. And, second, it does not make the property immutable, meaning that your class can later be modified to change the property elsewhere. Usually, this first point would be where the real challenge is and led to a lot of properties that probably should have private setters instead of public setters.

The second way of handling this issue was to define a more thorough setter (which I’m not going to write out here) that checked the current value of the property and then set it if there was no value. The downside to this is that it required more code to accomplish, but it does allow you to use Nested Object Creation.

Neither of these solutions is very good. On the one hand, you have code that allows modification of a field that you want to have immutable. On the other hand, you have a a lot of manual code that could easily have bugs introduced or be difficult to understand. Usually, I’ve seen developers favor the first solution, as it is, strictly speaking, easier, and if you have control over your codebase, then there’s “no risk” to allowing a property to have a public setter.

With the changes in C# 9.0, you now have an easy way of making the field immutable while still allowing it to be set in both the constructor and during Nested Object Creation. Additionally, since the field is only set during initialization, you can do fun things, like set default values and throw exceptions if a value isn’t passed:

public class Person
{
private readonly string firstName = "<unknown>";
private readonly string lastName = "<unknown>";

public string FirstName
{
get => firstName;
init => firstName = (value ?? throw new
ArgumentNullException(nameof(FirstName)));
}
public string LastName
{
get => lastName;
init => lastName = (value ?? throw new
ArgumentNullException(nameof(LastName)));
}
}

In total, this is a solid improvement that I am very happy to see in C# 9.0. I can think of a number of times in the last couple years that having the init keyword would have either saved time or made my code more secure. I look forward to seeing more improvements in this direction from the DotNet team.

--

--