C# 9 - Immutability - Records & Init-only Properties

Immutability has been getting popular these last years, especially with the rise of not only functional programming and but also JS frameworks such as React.

It's an important concept for many reasons, but I won't get into it in this blog post because it's not the point. Although I would urge you to either read The Dao of Immutability or watch Jon Skeet's The changing state of immutability in c# video, which explain it in details.

The true constant is change. Mutation hides change. Hidden change manifests chaos. Therefore, the wise embrace history.

C# 9 is trying to embrace Immutability more, by introducing a new type, record and init-only properties. I finally had the time to play with them, and I'm very excited for when C# 9 will be officially out!

How to try C# 9 features

I'm sure some of you would like to try the new C# 9 features, here's how/where I do it:

  • LINQPad 6 Beta: by activating the experimental .NET 5 runtime.
  • SharpLab: by choosing the master Roslyn branch.

Init-only Properties

Imagine you have the following class:

public class Rectangle
{
  public double Width { get; set; }
  public double Height { get; set; }
}

Now you can create instances of it:

var item1 = new Rectangle
{
  Width = 10,
  Height = 5
};

Which is fine, but we can change Width and Height whenever we want. What if we don't want that? What if we want our Rectangle to be immutable?

public class Rectangle
{
  public double Width { get; }
  public double Height { get; }

  public Rectangle(double width, double height)
  {
    Width = width;
    Height = height;
  }
}

As you can see, we'll need to make the properties read-only and add a constructor that fills them. This just adds boilerplate (a parameterized constructor) and removes the possibility to use object initializers.

The new init keyword comes to the rescue:

This means that we can only change (set) the values of Width and Height when instantiating Rectangle. After that, you can't change the values of these properties, which essentially makes our class immutable!

Records

If you want to read the official proposal, which contains the full details, visit records.md.

Init-only properties are already doing a good job to promote immutability in C#, so you might be wondering why records are such an important addition to the language.

Records are here for two main reasons:

  1. A lot of our classes are “data-holders”, and creating them requires a lot of boilerplate.
  2. Immutability can be enhanced by things like value-based equality and deep cloning, which are automatically generated for you.

Deep dive

In order to make our Rectangle a record, all we need is to change the class keyword:

public record Rectangle
{
  public double Width { get; init; }
  public double Height { get; init; }
}

Which will be turned to this by the compiler:

Here are the key things that you need to know about:

  • Line 11: A generated property used in the Equals method to only compare two instances of the same type, so inheritance won't matter.
  • Line 49: A generated Clone method that uses the copy constructor to clone the instance.
  • Line 60: A generated override of Object.Equals.
  • Line 65: A generated virtual Equals method that does a full value-based comparison.
  • Line 70: A generated copy constructor that copies the values of all the fields.
  • Line 80: A generated IEquatable<Rectangle>.Equals that uses the generated Equals method, since our generated class implements IEquatable<Rectangle>.
If your record already contains a method/constructor that is supposed to be generated, for example the copy constructor, then the compiler will use your implementation and not generate a new one.

Ways to define records

There are a couple of ways to define records:

With-expressions

Since we're working with immutable data, a core concept is to create new object from existing one, by changing one or more properties. C# 9 adds the with expression, which is just a syntax sugar:

Here's the actual generated code:

As you can see, the with expressions turn into a Clone + manual set of the changed properties. Note that with works with init properties because it uses the object initializer syntax.

If you noticed, the generated code contains duplicate objects, for example rectangle3. If anyone knows why, please send me a message!

Conclusion

Init-only properties and Records will boost our productivity and help us embrace immutability into our software. Personally, I can't wait to use this in Blazor to manage state.

C# 9 is without a doubt a great milestone for the language. I only presented two features, but the version comes with a lot more! Make sure to check them out here.

Hope you enjoyed the post, see you soon!

Zanid Haytam Written by:

Zanid Haytam is an enthusiastic programmer that enjoys coding, reading code, hunting bugs and writing blog posts.

comments powered by Disqus