Immutability

May 11th, 2020

It's such a simple thing that it almost doesn't seem worth dedicating an article to. Also, there are plenty articles on the internet that already explain the exact same thing that I am going to explain right now. So why does this bear repeating? Because immutability is an essential building block of good software design, and my other articles will make use of this building block.

Also, despite it being such a simple thing to apply, the number of codebases I have encountered in the world of corporate software that actually applied immutability, and which weren't primarily written by myself, have been exactly zero. I find that shocking, so articles like these remain necessary.

Refcatoring towards immutability

Let me start by showing how to turn a mutable object into an immutable one. Take a look at the following class:

public sealed class License
{
	public DateTime StartDate { get; set; }
	public DateTime EndDate { get; set; }
}

The setters make this object mutable. Any client can set the properties of the License to whatever they want. Also, any client creating a License object can choose not to instantiate the properties.

Creating a License may look like this:

var license = new License
{
	StartDate = new DateTime(2020, 01, 01),
	EndDate = new DateTime(2020, 12, 31)
};

This may look very practical, but actually it's dangerous. We get to that in a moment, first let's refactor the object to make it immutable. To do so we have to get rid of the setters, and instead instantiate the properties through the constructor.

Here's the resulting License class:

public sealed class License
{
	public License(DateTime startDate, DateTime endDate)
	{
		StartDate = startDate;
		EndDate = endDate;
	}

	public DateTime StartDate { get; }
	public DateTime EndDate { get; }
}

And here's the client's code:

var license = new License
(
	startDate: new DateTime(2020, 01, 01),
	endDate: new DateTime(2020, 12, 31)
);

Why immutability matters

By making the object immutable we can be sure that the object, once created, is always in a complete and valid state. For example, we cannot accidentally forget to set an EndDate when creating the immutable License object. The constructor makes the properties required.

Immutability makes it easier to reason about the code, and thereby reduces the chance of introducing bugs into the code. This is especially true in cases where concurrent threads mutate the state of an object. Don't take my word for it. Give this a serious try in one of your own projects and see what a difference it makes.

Making changes to an immutable object

What if we do want to make changes to an immutable object? For example, what if we wanted to extend our License with one year? We do that by creating and returning a new License object.

Here's an example of how to do that:

public sealed class License
{
	public License(DateTime startDate, DateTime endDate)
	{
		StartDate = startDate;
		EndDate = endDate;
	}

	public DateTime StartDate { get; }
	public DateTime EndDate { get; }
	public License ExtendWithOneYear() => new License(StartDate, EndDate.AddYears(1));
}

And here's the client code:

license = license.ExtendWithOneYear();

Conclusion

Making your objects immutable is a simple, important building block of good software design. It makes code easier to understand, and prevents bugs. Try it.