DEV Community

Dave Brock
Dave Brock

Posted on • Originally published at daveabrock.com on

C# 9 Deep Dive: Init-only features

A few weeks ago, we took a quick tour of some upcoming C# 9 features that will make your development life easier. We dipped our toes in the water. But now it’s time to dig a little deeper.

I’m starting a new series over the next several weeks, that will showcase all of the announced features incrementally. Then, we will tie it all together with an all-in-one app. As for the features we are showing off, we could always dig deeper by what we see in the Language Feature Status in GitHub, but the publicly-announced-at-Build features will most likely make it when .NET 5.0 launches in November 2020.

Here’s what we’ll be walking through:

  • Post 1 (this post) - Init-only features
  • Post 2 (future post) - Records
  • Post 3 (future post) - Pattern matching
  • Post 4 (future post) - Target typing and covariant returns
  • Post 5 (future post) - Putting it all together: an all-in-one application

Heads up! C# 9 is still in preview mode, so much of this content might change. I will do my best to update it as I come across it, but that is not guaranteed. Have fun, but your experience may vary.

Init-only features

With C# 9, the team is shipping a bunch of great init-only features. This includes init-only properties, init accessors, and readonly fields.

Init-only properties

For virtually all of your C# life, you’ve done something like the following:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string FavoriteColor { get; set; }
    // and so on...
}
Enter fullscreen mode Exit fullscreen mode

Of course, you can throw a private set in there for access control, but your options have typically been limited for your getters and setters. However, this forces mutability when you perform object initialization. For this to work, properties must be mutable. To set values in a new Person, you’ll need to call the object’s constructor (in this case, as in most cases, a parameterless constructor) and then perform assignment from the setters.

To initialize a new Person, you’re used to doing this:

var person = new Person
{
    FirstName = "Tony",
    LastName = "Stark",
    Address = "10880 Malibu Point",
    City = "Malibu",
    FavoriteColor = "Red"
};
Enter fullscreen mode Exit fullscreen mode

And since this is mutable, you can easily do this:

Console.WriteLine(person.FirstName); // Tony
person.FirstName = "Howard";
Console.WriteLine(person.FirstName); // Howard
Enter fullscreen mode Exit fullscreen mode

With C# 9, we can change this with an init accessor. This means you can only create and set a property when you initialize the object. If we modify our Person model like this, we can prevent the FirstName from being changed:

public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string FavoriteColor { get; set; }
    // and so on...
}
Enter fullscreen mode Exit fullscreen mode

With this, that means this code will work:

var person = new Person
{
    FirstName = "Tony",
    LastName = "Stark",
    Address = "10880 Malibu Point",
    City = "Malibu",
    FavoriteColor = "Red"
};
Enter fullscreen mode Exit fullscreen mode

However, when you try to execute this code:

person.FirstName = "Howard";
Enter fullscreen mode Exit fullscreen mode

The compiler will not be happy.

Init accessors and read-only fields

As we just saw, init accessors can only be called when you initialize. If you wish to work with readonly fields, the mutability only applies during initialization, just as with non-read-only ones.

With this in mind, using private fields will set a value during initialization - otherwise, we will have an ArgumentNullException thrown:

public class Person
{
    private readonly string _firstName;
    private readonly string _lastName;
    private readonly string _address;
    private readonly string _city;
    private readonly string _favoriteColor;

    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)));
    }
    public string Address
    {
        get => _address;
        init => _address = (value ?? throw new ArgumentNullException(nameof(Address)));
    }
    public string City
    {
        get => _city;
        init => _city = (value ?? throw new ArgumentNullException(nameof(City)));
    }
    public string FavoriteColor
    {
        get => _favoriteColor;
        init => _favoriteColor = (value ?? throw new ArgumentNullException(nameof(FavoriteColor)));
    }
}
Enter fullscreen mode Exit fullscreen mode

What’s next

In this post, we learned how to make individual properties become immutable. If you want this behavior for your entire object, you’ll want to work with records - one of the best new features in C# 9, in my opinion. Stay tuned for my next post to discuss this.

Top comments (0)