top of page

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

Writer's pictureAhmed Tarek

Why Immutability Is Important in .NET C#

Updated: Apr 17

Why Immutability is a good thing and how to apply it.


Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Why Immutability Is Important in .NET C#. Image by Ahmed Tarek

This is one of the most interesting topics to me to the extent that once I start talking about it you might need to fight me to stop 😊


I believe that many of the issues and problems that developers come across could be solved if they apply Immutability as it should.


Don’t believe me? Let me show you.


 

Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
The Definition. Photo by Romain Vignes on Unsplash, adjusted by Ahmed Tarek

The Definition


In the software world, Immutability means that you define your classes and structs in a way that the object could not be manipulated or updated once it is created.


For example, let’s say that you have this class:


public class Employee
{
  public string FirstName { get; set; }

  public Employee(string firstName)
  {
    FirstName = firstName;
  }
}

Now, creating an instance of the Employee class could be as easy as the following:


var ahmed = new Employee("Ahmed");

However, you can still update the FirstName of the object ahmed by doing the following:


ahmed.FirstName = "Tarek";

In this case, the Employee class is not following the definition of Immutability.


If we want to fix the Employee class to really follow the definition of Immutability, we need to update the class as follows:


public class Employee
{
  public string FirstName { get; }

  public Employee(string firstName)
  {
    FirstName = firstName;
  }
}

As you can notice, we removed the set; from the FirstName property so that the only way to set its value is through the constructor. This means that the object could not be modified after being created.


Now, you might think:

Is that all? It is too easy…

However, there are some mistakes that some developers might do. They might think that they did what is needed to make their classes and structs immutable when they actually didn’t.


Let me show you.


 


 

Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Common Mistakes Violating Immutability. Photo by Caspar Rae on Unsplash, adjusted by Ahmed Tarek

Common Mistakes Violating Immutability


These are some of the mistakes that developers might do violating the immutability rules.


Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Private Setter. Image by Ahmed Tarek

Private Setter


Let’s say that we have the following code:


public class Employee
{
  public string FirstName { get; private set; }

  public Employee(string firstName)
  {
    FirstName = firstName;
  }
}

As you can notice here, we don’t have a public setter on the FirstName property, however, we still have a private setter which opens the door to internal manipulations.


This means that any piece of code inside the Employee class can still update the value of the FirstName property which leaves the object mutated.


Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Internal Manipulative Code. Image by Ahmed Tarek

Internal Manipulative Code


Let’s say that we have the following code:


public class Employee
{
  public string FirstName { get; private set; }

  public Employee(string firstName)
  {
    FirstName = firstName;
  }

  public void Rename()
  {
    FirstName += " "; // adds a trailing white space character
  }
}

As you can notice here, the Employee class exposes a Rename method that would update the value of the FirstName property. This for sure leaves the object mutated.


Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Read/Write Collections. Image by Ahmed Tarek

Read/Write Collections


Let’s say that we have the following code:

public class Employee
{
  public string FirstName { get; }

  public List<Employee> Manage { get; }

  public Employee(string firstName, List<Employee> manage = null)
  {
    FirstName = firstName;
    Manage = manage?.ToList() ?? new List<Employee>(); // to copy the items from "manage"
  }
}

As you can notice here, the Managed property is not exposing a public setter or even a private one. However, the type of the property itself is a List which is open for write.


This means that we can do the following:


var ahmed = new Employee("Ahmed");
var tarek = new Employee("Tarek");
var hasan = new Employee("Hasan", new List<Employee> { ahmed, tarek });

// manipulative code
hasan.Manage.Add(new Employee("Saleh"));

See, here we can add a new Employee instance inside the Manage list. This for sure leaves the object mutated.


Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
ReadOnly Collections of Mutable Objects. Image by Ahmed Tarek

ReadOnly Collections of Mutable Objects


Let’s say that we have the following code:


public class TelephoneNumber
{
  public string Number { get; set; } // this is mutable

  public TelephoneNumber(string number)
  {
    Number = number;
  }
}

public class Employee
{
  public string FirstName { get; }

  public IReadOnlyCollection<TelephoneNumber> TelephoneNumbers { get; }
  
  public Employee(string firstName, TelephoneNumber[] telephoneNumbers = null)
  {
    FirstName = firstName;
    TelephoneNumbers = telephoneNumbers?.ToArray() ?? Array.Empty<TelephoneNumber>(); // to copy the items from "telephoneNumbers"
  }
}

As you can notice here, the TelephoneNumbers property doesn’t expose any public or private setters, and its type is IReadOnlyCollection which doesn’t allow for adding or removing items from inside.


Now, you might think that your object is safe and no one can mutate it, right?


Let me show you something. Let’s say that we have the following code:


var homeTelephoneNumber = new TelephoneNumber("123");
var mobileTelephoneNumber = new TelephoneNumber("456");
var officeTelephoneNumber = new TelephoneNumber("789");

var allTelephoneNumbers = 
  new TelephoneNumber[]
  {
    homeTelephoneNumber,
    mobileTelephoneNumber,
    officeTelephoneNumber
  };

var ahmed = new Employee("Ahmed", allTelephoneNumbers);

// manipulative code
ahmed.TelephoneNumbers.ElementAt(0).Number = "555";

See, we can still manipulate the object because the underlying object inside the IReadOnlyCollection is mutable.


 


 

Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Mutable Member Object. Image by Ahmed Tarek

Mutable Member Object


Let’s say that we have the following code:


public class TelephoneNumber
{
  public string Number { get; set; } // this is mutable

  public TelephoneNumber(string number)
  {
    Number = number;
  }
}

public class Employee
{
  public string FirstName { get; }

  public TelephoneNumber TelephoneNumber { get; }
  
  public Employee(string firstName, TelephoneNumber telephoneNumber)
  {
    FirstName = firstName;
    TelephoneNumbers = telephoneNumbers;
  }
}

As you can notice here, although the TelephoneNumber property is not exposing any public or private setters, still, the type of the property is TelephoneNumber which is mutable.


This means that we can write some code like this:


var homeTelephoneNumber = new TelephoneNumber("123");
var ahmed = new Employee("Ahmed", homeTelephoneNumber);

// manipulative code
ahmed.TelephoneNumber.Number = "555";

This for sure leaves the object mutated.


Now I can hear you saying:

What is this?!!! I now think it is impossible to achieve Immutability. How can we write code which doesn’t violate any of these rules?

Take it easy, you might think so but actually, it is not impossible.


In the next section, I will show you some practices that can help you with this.


 

Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
How To Not Violate. Image by Ahmed Tarek

How To Not Violate


There are some golden rules to follow to achieve Immutability:

  • All fields should be readonly.

  • All properties should provide getters only.

  • All members should be of immutable types.

  • All collections should be Immutable. Check this out.

  • In case a method needs to update the internal state, it should keep the internal state untouched and return a whole new object with the new state.


If you follow these rules, you would end up with a fully immutable object.


Ok, now I can hear you asking:

Does it really worth the hassle? Would I gain anything from doing all of this?

Absolutely. Although it seems too much to follow these rules, believe me, you would get many benefits in return.


Let me show you…


 


 

Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Advantages of Immutability. Photo by Den Harrson on Unsplash, adjusted by Ahmed Tarek

Advantages


Based on the fact that the object could not be mutated after being created, there are a lot of advantages we can get.


Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Thread Safety. Image by Ahmed Tarek

Thread Safety


Since the object is guaranteed to be immune against any manipulation, we don’t care if the object would be accessed by one thread or more.


At the end, all these threads would not be able to update the object, so, why not to be accessed by more than one thread?


Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Singleton. Image by Ahmed Tarek

Singleton


Since the object would always hold one and only one state at a time, then we can use this object as a singleton.


It is like integers; if I tell you that you have an integer with the value 3, would you ask me which instance? I think no as it doesn’t matter, if it is 3 then it will always be 3.


Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Less Memory Allocations. Image by Ahmed Tarek

Less Memory Allocations


This is because you can define the object one time and use it many times. You can also define it as a singleton. You can also cache it somewhere.


Additionally, you can avoid automatic copying by passing the object by reference or using the in parameter.


All of this would save some memory allocations.


Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Compiler Optimizations. Image by Ahmed Tarek

Compiler Optimizations


Defining a struct as immutable would make it so easy for you to also define it as read-only using the readonly keyword.


This would also help the compiler avoid the defensive copy mechanism which would eventually save processing and memory leading to a performance boost.


Note: If you don’t know much about the defensive copy mechanism, you can read the article Defensive Copy In .NET C#.


Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Caching. Image by Ahmed Tarek

Caching


Since the object would not be manipulated after being created, the object could be used as a key in a dictionary, or safely cached in a cache collection.


Additionally, it would be easy to avoid duplications which would save memory.


Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Comparisons. Image by Ahmed Tarek

Comparisons


If you pass the object to a method that would return a new object, it would be easy to compare both objects because you are sure that your original input object would still be the same.


Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Unit Testing. Image by Ahmed Tarek

Unit Testing


As we mentioned in the Comparisons section above, you can easily compare objects before and after an operation. This would be so helpful while writing your unit tests.


 

Immutability Why How Apply Important Good Right Immutable Collections Singleton Memory Allocation Compiler Optimization Caching Unit Testing DotNet .NET CSharp C# Code Coding Programming Software Design Development Engineering Architecture Best Practice Ahmed Tarek
Final Thoughts. Photo by Kenny Eliason on Unsplash, adjusted by Ahmed Tarek

Final Thoughts


That’s it. I hope starting from this moment you will appreciate Immutability and try to apply it as far as you can.


Finally, I hope you enjoyed reading this article as I enjoyed writing it.



Recent Posts

See All

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

bottom of page
Mastodon Mastodon