C# 9.0: Init-only Properties – Create Immutable Properties Without Constructor Boilerplate

In the previous blog post you learned about C# 9.0 top-level statements. In this blog post you learn about another C# 9.0 feature that is called init-only properties.

In November 2007 Microsoft introduced C# 3.0 with the release of .NET Framework 3.5. C# 3.0 introduced many new concepts, like for example Language Integrated Query (LINQ) syntax. It also introduced the powerful concept of object initializers. They are the foundation for the new init-only properties that are introduced with C# 9.0, so let’s start here with object initializers.

Understand Object Initializers

Let’s say you have a Friend class with the two properties FirstName and LastName:

public class Friend
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Traditionally, you can create a Friend object like below by calling the default constructor and initializing the properties after that:

var friend = new Friend();
friend.FirstName = "Thomas";
friend.LastName = "Huber";

Instead of the three statements as above, you can also use a single statement with an object initializer like in the code snippet below. The object initializer also calls the default constructor and then it initializes the two properties FirstName and LastName by calling their setters:

var friend = new Friend
{
    FirstName = "Thomas",
    LastName = "Huber"
};

Creating Immutable Properties

The problem with object initializers is that they don’t allow you to create immutable properties – at least not before C# 9.0. Immutable properties are properties that you can’t change after you created the object. But in our case, you can change for example the FirstName property of a Friend at any time after you created the Friend object. The snippet below shows this:

var friend = new Friend
{
    FirstName = "Thomas",
    LastName = "Huber"
};

friend.FirstName = "Claudius";

To make the properties immutable, you have to create a constructor as in the snippet below that takes the firstname and lastname as parameters. Note that I’ve removed the setters of the properties, which means the properties can’t be changed after the Friend object was initialized. Defining just a getter with an Auto Property is a so-called get-only Auto Property. It’s a feature that was introduced with C# 6.0 and .NET Framework 4.6 in 2015. Get-only Auto Properties can only be initialized directly or like in the snippet below in the constructor. This is the same logic as with readonly fields, they can also be initialized directly or in a constructor.

public class Friend
{
    public Friend(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public string FirstName { get; }
    public string LastName { get; }
}

With the Friend class defined as above, you can’t change the FirstName or the LastName property after you created the Friend object. Below I try this, you see I get an error that says that the FirstName property is read only.

Now we solved our problem, the properties FirstName and LastName are immutable. But to achieve this, we had to define a constructor.

With C# 9.0 you can create immutable properties without constructor boilerplate: This is possible with the new init-only properties.

The Concept of Init-Only Properties in C# 9.0

In the code snippet below you see a Friend class that looks exactly the same as the Friend class defined in the previous code snippet. And it works exactly the same. But can you spot the little difference?

public class Friend
{
    public Friend(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public string FirstName { get; init; }
    public string LastName { get; init; }
}

The difference is the init keyword used for the Auto Properties. The init keyword is used to define a special kind of set accessor (This means, you get an error if you use init and set in a single Auto Property). These properties are now so-called init-only properties. It means, you can only initialize these properties in the constructor like in the snippet above, or directly like in the snippet below….

public string FirstName { get; init; } = "Thomas";

… or *drumrolls*… in an object initializer. This makes the constructor that we used above unnecessary to define immutable properties. Let’s look at this.


Init-only Properties and Object Initializers

In the snippet below you see a Friend class with init-only properties, and without a constructor. Actually, this Friend class looks exactly the same as the Friend class shown at the beginning of this blog post. The only difference is that we’re using the init keyword instead of the set keyword for our Auto Properties, which makes them init-only properties.

public class Friend
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

That means, as mentioned before, that you can initialize the properties only in a constructor, directly or in an object initializer. Our Friend class does not have a constructor, so let’s use an object initializer:

var friend = new Friend
{
    FirstName = "Thomas",
    LastName = "Huber"
};

Works like a charm. But now the great thing is that the property cannot be changed after the object was created. And that’s the power of init-only properties. In the screenshot below I try to set the FirstName property after we initialized the Friend object. As you can see, I get an error that says that the init-only property FirstName can only be assigned in an object initializer, constructor or init accessor.

Values for Init-only Properties Are Not Required

Important to mention is that values for init-only properties are not required, they’re optional. You can also set just the properties you want, as you would with normal Auto Properties. For example you could only set the FirstName property of a Friend object as in the snippet below, that’s completely valid. But as we use init-only properties, you have no chance to set the LastName property after this statement, as the properties are immutable:

var friend = new Friend
{
    FirstName = "Thomas",
};

Setting Readonly Fields

As the init accessor of an init-only property is called during object initialization, it is allowed to set readonly fields in the init accessor, exactly in the same way as you could set them in a constructor. This is useful if you want to do checks on the assigned property value. For example assigning null or whitespace makes not much sense for the properties FirstName and LastName. So, you could create the Friend class like below and set readonly fields in the init accessors of the properties. The properties are still immutable, but if you assign null or whitespace in an object initializer – that’s the only way to set these properties of that class as no constructor is defined – you get an ArgumentException.

public class Friend
{
    private readonly string _firstName;
    private readonly string _lastName;

    public string FirstName
    {
        get => _firstName;
        init => _firstName = string.IsNullOrWhiteSpace(value)
            ? throw new ArgumentException("Shouldn't be null or whitespace",
                nameof(FirstName))
            : value;
    }
    public string LastName
    {
        get => _lastName;
        init => _lastName = string.IsNullOrWhiteSpace(value)
            ? throw new ArgumentException("Shouldn't be null or whitespace",
                nameof(LastName))
            : value;
    }
}

Summary

Init-only properties are a powerful feature. They allow you to create immutable properties without defining a constructor that takes the initial property values. While setting init-only properties from a constructor works, you can also set init-only properties with an object initializer, and later you can’t modify them, as they are immutable. This is definitely a very useful feature if you plan to work with immutable data in C#.

Beside init-only properties there’s even more in C# 9.0 to work with immutable data: In the next blog post you learn about record types.

Happy coding,
Thomas

Share this post

Comments (2)

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.