How to use immutability in C#

By FoxLearn 1/6/2025 3:58:17 AM   63
Immutability, a feature in functional programming languages, simplifies writing, testing, and maintaining programs.

However, it is not supported by many imperative languages, including earlier versions of C#. C# 9 introduces records, enabling immutability, and it's available in .NET 5 preview. Prior to that, immutability could be implemented in C# through the System.Collections.Immutable namespace as a NuGet package. An immutable object cannot be changed once created, making it particularly useful for scenarios like Data Transfer Objects.

To work with immutable types in C#, you need to install the System.Collections.Immutable package from NuGet.

This can be done through the Visual Studio 2019 NuGet package manager or by running the command Install-Package System.Collections.Immutable in the NuGet package manager console.

Understand immutability and records in C# 9

Data Transfer Objects (DTOs) are a common use case for immutability, as they are often serialized to be independent of the consumer's technology. To create immutable DTOs, you can use a ReadOnlyCollection or thread-safe immutable collection types from the System.Collections.Immutable namespace. Alternatively, C# 9 offers record types, which are lightweight, immutable data types with only read-only properties. Record types are thread-safe and cannot be changed after creation.

You can initialize a record type only within a constructor.

For example, consider a class representing a Book with properties like Title, Author, and PublishedYear.

class Book(string Title, string Author, int PublishedYear);

Alternatively, you could declare the Book record type like this:

public record class Book {
    public string Title { get; init; }
    public string Author { get; init; }
    public int PublishedYear { get; init; }
}

Note the use of the record keyword in the class declaration, which makes it immutable. A record type allows passing data across layers while ensuring immutability of DTOs (Data Transfer Objects).

The System.Collections.Immutable namespace provides immutable collections, where elements cannot be changed once created.

For example, the ImmutableStack allows you to push and pop elements just like a mutable stack, but since it's immutable, the original stack remains unaltered. When an element is popped, a new stack is created:

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

The following code demonstrates how the original stack remains unchanged after popping an element:

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();
    }
}

Output:

No of elements in original stack: 10
No of elements in new stack: 9

As shown, the original stack remains unchanged, and a new stack with fewer elements is created. Immutable collections don’t have constructors, but you can create them using the static Create method, like so:

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

If you modify this collection, a new immutable list is created, and the original list remains unchanged. Immutability ensures that once a type is created, it cannot be altered, offering thread safety and predictable behavior.