Functional C#: Immutability

By Vladimir Khorikov

The topic described in this article is a part of my Applying Functional Principles in C# Pluralsight course.

I’m starting a series of articles in which I want to show how to program in C# in a more functional way.

Why immutability?

The biggest problem of enterprise software development is code complexity. Code readability is probably the first goal you should try to achieve on your way to building a software project. Without it, you are not able to make a qualified judgment about the correctness of your software or at least your ability to reason about it is significantly reduced.

Do mutable objects increase or reduce code readability? Let’s look at an example:

// Create search criteria

var queryObject = new QueryObject<Customer>(name, page: 0, pageSize: 10);

 

// Search customers

IReadOnlyCollection<Customer> customers = Search(queryObject);

 

// Adjust criteria if nothing found

if (customers.Count == 0)

    AdjustSearchCriteria(queryObject, name);

 

// Is queryObject changed here?

Search(queryObject);

Has the query object been changed by the time we search customers for the second time? Maybe yes. But maybe not. It depends on whether or not we found anything for the first time and on whether or not AdjustSearchCriteria method changed the criteria. To find out what exactly happened, we need to look at the AdjustSearchCriteria method code. We can’t know it for sure by just looking at the method signature.

Now compare it to the following code:

// Create search criteria

var queryObject = new QueryObject<Customer>(name, page: 0, pageSize: 10);

 

// Search customers

IReadOnlyCollection<Customer> customers = Search(queryObject);

 

if (customers.Count == 0)

{

    // Adjust criteria if nothing found

    QueryObject<Customer> newQueryObject = AdjustSearchCriteria(queryObject, name);

    Search(newQueryObject);

}

It is now clear that AdjustSearchCriteria method creates new criteria that are used to perform a new search.

So, what are the problems with mutable data structures?

  • It is hard to reason about the code if you don’t know for sure whether or not your data is changed.
  • It is hard to follow the flow if you need to look not only at the method itself, but also at the methods it calls.
  • If you are building a multithreaded application, following and debugging the code becomes even harder.

How to build immutable types

If you have a relatively simple class, you should always consider making it immutable. This rule of thumb correlates with the notion of Value Objects: value objects are simple and easily made immutable.

So how do we build immutable types? Let’s take an example. Let’s say we have a class named ProductPile representing a bunch of products we have for sale:

public class ProductPile

{

    public string ProductName { get; set; }

    public int Amount { get; set; }

    public decimal Price { get; set; }

}

To make it immutable, we need to mark its properties as read-only and create a constructor:

public class ProductPile

{

    public string ProductName { get; private set; }

    public int Amount { get; private set; }

    public decimal Price { get; private set; }

 

    public ProductPile(string productName, int amount, decimal price)

    {

        Contracts.Require(!string.IsNullOrWhiteSpace(productName));

        Contracts.Require(amount >= 0);

        Contracts.Require(price > 0);

 

        ProductName = productName;

        Amount = amount;

        Price = price;

    }

}

Let’s say we need to reduce the product amount by one when we sell one of the items. Instead of changing the existing object we need to create a new one based on the current:

public class ProductPile

{

    public string ProductName { get; private set; }

    public int Amount { get; private set; }

    public decimal Price { get; private set; }

 

    public ProductPile(string productName, int amount, decimal price)

    {

        Contracts.Require(!string.IsNullOrWhiteSpace(productName));

        Contracts.Require(amount >= 0);

        Contracts.Require(price > 0);

 

        ProductName = productName;

        Amount = amount;

        Price = price;

    }

 

    public ProductPile SubtractOne()

    {

        return new ProductPile(ProductName, Amount – 1, Price);

    }

}

So what do we get here?

  • With immutable class, we need to validate its code contracts only once, in the constructor.
  • We absolutely sure that objects are always in a correct state.
  • Objects are automatically thread-safe.
  • The code’s readability is increased as there’s no need to step into the methods for making sure they don’t change anything.

What are the limitations?

Of course, everything comes at a price. While small and simple classes benefit from immutability the most, such approach is not always applicable to larger ones.

First of all, there are performance issues attached. If your object is quite big, necessity to create a copy of it with every single change may hit the performance of your application.

A good example here is immutable collections. Their authors took into account potential performance problems and added Builder class that allows to mutate the collection. After the preparation is done, you can finalize it converting to an immutable collection:

var builder = ImmutableList.CreateBuilder<string>();

builder.Add(“1”);                                   // Adds item to the existing object

ImmutableList<string> list = builder.ToImmutable();

ImmutableList<string> list2 = list.Add(“2”);        // Creates a new object with 2 items

Another issue is that some classes are inherently mutable and trying to make them immutable brings more problems than solves.

But don’t let these issues keep you from creating immutable data types. Consider pros and cons of every design decision and always take common sense into account.

Summary

In most cases, you will be able to benefit from immutability, especially when you keep your classes small and simple.

Other articles in the series

LinkedInRedditTumblrBufferPocketShare




  • MichaelLPerry

    Great post on immutability. Unfortunately, making the auto property setters private does not make them immutable. The class can still change those properties itself. The only way to be sure that a property is immutable is to give it a readonly backing field. That way you don’t have to scan the implementation of the class to see if it’s changing the property; the compiler will do that for you.

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Thanks!

      You are right. If you need to look at the class’s implementation, readonly fields do a better job.

      On the other hand, the need of scanning implementation itself conflicts with encapsulation principle. So it doesn’t make a lot of difference whether you use readonly fields or properties with private setters: if you need to come down to the implementation details, then it’s some kind of a design smell.

      Unfortunately, in C#, there’s no way to avoid such smell: you can’t know whether or not the class is immutable in advance and have to either rely on conventions or look at its implementation. It would be great if we had some kind of a marker supported by compiler saying that the class is immutable:

      public immutable class ProductPile
      {
      /// …
      }

      In this case, we could get to know it just by looking at the class signature.

  • Franco Lázaro

    Suppose that you make a call to SubtractOne() in the ProductPile class, then, you’ll have an object with, for example, an Amount = 10, and another with Amount = 9. In a multithreaded application, will it be possible that one thread reads 10, and another 9? Doesn’t it lead to inconsistencies?

    • http://enterprisecraftsmanship.com/ Vladimir Khorikov

      Two threads will have their own copies of the ProductPile instance, so yes, they can at some point have copies that differ from each other. For example, one thread can call SubtractOne and replace its copy with the new one that have Amount = 9, whereas the other thread just keeps the old instance.

      Regarding inconsistencies, it does add them to some degree. It’s a trade-off between data isolation between threads and consistency. In most cases, isolation is more important as it prevents a lot of bugs from happening.

      • Franco Lázaro

        So, you say that it is preferrable to be consistent inside a thread (or client execution, to speak more generally), i.e., client A updates its ProductPile instance whereas client B can’t, and to manage the concurrency issues elsewhere.