Blog

11. August 2020

The charming way to immutability

Functional programming paradigms are gaining more and more popularity and hence they are getting more and more included in object oriented languages like Java (e.g. Streams with Java 8) and C# (e.g. LINQ since .NET Framework 3.5). A “big player” in functional programming is immutability of objects, which means you cannot manipulate objects after instantiation.

Advantages of immutability

One of the main advantages of immutability is – simply said – that manipulation of objects is not possible.

Consider having a multi-threaded environment. You always have to pay attention that one thread is not manipulating an object while the other is accessing it. This is normally achieved with locks and can become very complex. A simple example for an annoying bug every developer falls into – at least once in a lifetime 🙂 – is iterating over a list while another thread is removing or adding an item to that list. This leads to an exception.

“Ah come on – I know that. That cannot happen to me” – also fine, but there might be more complex – not so obvious bugs. Consider dealing with caching in a web project (maybe in a large legacy project). And some part of the code (in a jsp) is iterating over a list from that cache (self-implemented caching framework). And from time to time (not often but though a huge problem) a server is down. Users see a white page and you do not see anything in a log. Have fun!!

But immutability also has advantages in single-threaded environments. You can be sure that objects you deal with will not be manipulated. This is important because sometimes side effects of a method call are not that obvious and may result in bugs.

The overall goal is to prevent side effects when sharing objects and prevent bugs resulting from that side effects.

Dealing with immutability in C#

Immutability is always a bit clumpsy to deal with in object oriented languages. Normally you would define a class as immutable in C# like so:

public class Person
{
    public string Name { get; }

    public Person(string name)
    {
        Name = name;
    }
}

This has some disadvantages if you want to add or remove properties. You will always have to update the constructor and its references. Also if you have many properties your class will end up with an endless long constructor, which you may have to tackle with a builder pattern and replacing all direct calls to the constructor with that builder.

Common way of object creation in C#

During developing with C# I very often came up with property initializers to create an object (which is not possible in the Java world). However in C# you can create objects like so:

var person = new Person
{
    Name = "Hubert"
}

This way of object initialization is very handy and normally you’ll never need a kind of a builder because it will remain readable even if your class has many properties.

The drawback is that you loose immutability as the compiler complains that your properties must have a setter for property initialization.

C# 9 to the rescue

With C# 9 a great language feature will be introduced: init properties. This gives you the ability to make use of property initializers while keeping your class immutable. 👏

The Class Declaration will look like:

public class Person
{
    public string Name { get; init; }
}

Now you can create objects with:

var person = new Person
{
    Name = "Hubert"
}

A later assignment to that variable will result in a compiler error.

How to manipulate the state of an object?

Well, of course you need to be able to change values of objects but you do not change the state of that very object, instead you create a new object representing the new state.
Before C# 9 this is a very unhandy operation as you have to initialize all properties of the new object.

In C# 9 you can make use of the introduced record and with keywords. Records can be seen as POCOs, are meant to be immutable and you win some benefits like structural equality. They differ from structs in that they behave like classes and are accessed by reference (meaning that parameters are passed to methods by reference). So also recursive data is possible which is not in structs. Using our example a Person can have a public Person Father { get; init; } property.

Taking our previous example we add a birthdate and change the definition to:

public record Person
{
    public string Name { get; init; }
    public DateTime Birthdate { get; init; }

    public override string ToString()
        => $"{Name} born on {Birthdate:dd.MM.yyyy}";
}

Now we change the name by making use of the with operation:

var person = new Person
{
    Name = "Hubert",
    Birthdate = new DateTime(2020, 8, 25)
};

var changedPerson = person with { Name = "Christoph" };
Console.WriteLine(changedPerson);

The output is: Christoph born on 25.08.2020.

As mentioned before we can compare objects and win structural equality.

var hubert = new Person
{
    Name = "Hubert",
    Birthdate = new DateTime(2020, 8, 25)
};

var otherHubert = new Person
{
    Name = "Hubert",
    Birthdate = new DateTime(2020, 8, 25)
};

Console.WriteLine($"Equal: {hubert.Equals(otherHubert)}");

The output is: Equal: True

And to make it even more easy and readable we can declare Person like so (some of you might recognize some similarities to Kotlin language):

public record Person(string Name, DateTime Birthdate)
{
    public override string ToString()
        => $"{Name} born at {Birthdate:dd.MM.yyyy}";
}

Try out C# 9 language features

If you want to try out the newest C# 9 language features you will have to install the latest .NET 5 sdk (see https://dotnet.microsoft.com/download/dotnet/5.0).

Init properties are only included in v5.0.0-preview.7 but not in v5.0.0-preview.6!! Took me quite a time to find out what the problem was.

Also make sure you install / check the following steps:

  • Make sure the sdk’s preview version is supported by your Visual Studio IDE
    Check the Visual Studio support section on the .net 5 page. preview.7 is not working with Visual Studio < 16.7. It is now available but at the time of writing it was not. I had to install the preview version of Visual Studio 16.7 to make use of the preview.7 sdk. Check out this page: https://docs.microsoft.com/en-us/visualstudio/releases/2019/release-notes-preview.
  • Enable .NET Core preview SDKs
    If you do not have a Visual Studio preview version you have to enable the IDE to make use of preview SDKs.
    Go to Tools->Options and then under Environment->Preview Features. You need to check “Use previews of the .NET Core SDK (requires restart)”.
  • Make sure your .csproj file enables NET5 and C# 9. It should look like
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
    <LangVersion>preview</LangVersion>
  </PropertyGroup>
</Project>

When to use in production

The thing is that C# 9 will only be available in .NET 5 which is not yet released at the time of writing this blog. The planned release date is November 2020. If you want to use C# 9 you will have to wait for .NET 5 to be released.