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 archive 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:
var queryObject = new QueryObject<Customer>(name, page: 0, pageSize: 10);
IReadOnlyCollection<Customer> customers = Search(queryObject);
if (customers.Count == 0)
AdjustSearchCriteria(queryObject, name);
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:
var queryObject = new QueryObject<Customer>(name, page: 0, pageSize: 10);
IReadOnlyCollection<Customer> customers = Search(queryObject);
if (customers.Count == 0)
{
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 are 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"); ImmutableList<string> list = builder.ToImmutable();
ImmutableList<string> list2 = list.Add("2");
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
The post Functional C#: Immutability appeared first on Enterprise Craftsmanship.