Immutability is a feature of functional programming languages that makes programs easier to write, test, and maintain. However, immutability is not supported by many imperative programming languages. Until recently, C# did not support immutability out-of-the-box. 

That changes with the introduction of records in C# 9, which is available for preview in .NET 5. However, we can implement immutability in earlier versions of C# by using the System.Collections.Immutable namespace, which is available as a NuGet package. 

An immutable object is defined as an object that cannot be changed after it has been created. For many use cases, such as Data Transfer Objects, immutability is a desirable feature. This article discusses why we might want to take advantage of immutability and how we can implement immutability in C#.

To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here

Create a .NET Core console application project in Visual Studio

First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new .NET Core console application project in Visual Studio.

  1. Launch the Visual Studio IDE.
  2. Click “Create new project.”
  3. In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
  4. Click Next. 
  5. In the “Configure your new project” window shown next, specify the name and location for the new project.
  6. Click Create. 

This will create a new .NET Core console application project in Visual Studio 2019. We’ll use this project to illustrate immutability in the subsequent sections of this article.

Install the System.Collection.Immutable NuGet package

To work with immutable types, you should install the System.Collections.Immutable package from NuGet. You can do this either via the NuGet package manager inside the Visual Studio 2019 IDE, or by executing the following command in the NuGet package manager console:

Install-Package System.Collections.Immutable

This package comprises a collection of thread-safe classes, also known as immutable collections.

Understand immutability and records in C# 9

A Data Transfer Object is a classic example of when you want immutability. An instance of a DTO is often serialized so that it can be independent of the technology used at the consumer end. Naturally, when transferring a data object between a database and a client, you would like to ensure that the object cannot be changed — and that is exactly the purpose of a DTO. You can read more about the use of Data Transfer Objects in C# from my earlier article here

To create immutable DTOs, you can take advantage of a ReadOnlyCollection or the thread-safe immutable collection types in the System.Collections.Immutable namespace. Alternatively, you could take advantage of record types in C# 9 to implement immutable DTOs.

A record type in C# 9 is a lightweight, immutable data type (or a lightweight class) that has read-only properties only. Since a record type is immutable, it is thread-safe and cannot mutate or change after it has been created.

You can initialize a record type only inside a constructor. Creating a record type for a class (Author in this example) is as simple as the following code snippet.

class data Author(int Id, string firstName, string lastName, string address);

You also could write the Author record type as shown in the code snippet given below:

public data class Author {
public int Id { get; init; }
public string firstName { get; init; }
public string lastName { get; init; }
public string address { get; init; }
}

Note the usage of the data keyword when declaring the record type. The data keyword when used in the declaration of a class marks the type as a record. You can take advantage of an instance of record type to pass data across the layers while at the same time ensuring immutability of the DTOs.

The System.Collections.Immutable namespace

Immutable collections are those whose members cannot change once they have been created. The System.Collections.Immutable namespace comprises several immutable collections. This namespace contains immutable versions of Lists, Dictionaries, Arrays, Hashes, Stacks, and Queues.

The ImmutableStack<T> can be used to push and pop elements much the same way we do with mutable stacks. However, since ImmutableStack<T> is an immutable collection, its elements cannot be altered. So, when you make a call to the pop method to pop an element from the stack, a new stack is created for you and the original stack remains unaltered.

Let’s illustrate this with an example. The following code snippet shows how you can push elements onto an immutable stack.

var stack = ImmutableStack<int>.Empty;
for(int i = 0; i < 10; i++)
{
stack = stack.Push(i);
}

The following program demonstrates that the elements of an immutable stack cannot be altered.

class Program
{
static void Main(string[] args)
{
var stack = ImmutableStack<int>.Empty;
for(int i = 0; i < 10; i++)
{
stack = stack.Push(i);
}
Console.WriteLine("No of elements in original stack:
"+stack.Count());
var newStack = stack.Pop();
Console.WriteLine("No of elements in new stack: " +
newStack.Count());
Console.ReadKey();
}
}

When you execute the above program, here’s how the output should appear in the console window.

csharp immutability IDG

Figure 1

As you can see in Figure 1, the original immutable stack (containing 10 elements) is unchanged after a call to the Pop() method. Rather, a new immutable stack is created with 9 elements.

Immutable collections don’t offer constructors but you can take advantage of the static factory method called Create as shown in the code snippet given below.

var list = ImmutableList.Create(1, 2, 3, 4, 5);

If you wanted to add or remove an element from this collection, a new immutable list would be created and the original immutable list would remain unchanged.

Immutability is a design choice; it means that an instance of a type cannot be changed after it has been created. Except for immutable stacks and immutable queues, all immutable collections are based on AVL trees. Hence you can insert elements at any position of the collection, i.e., the beginning, middle, or end, without needing to copy the tree in its entirety.

How to do more in C#: