📜 ⬆️ ⬇️

Functional C #: Immutability

This is the first article in a small series on C # programming in a functional style. The series is not about LINQ, as one might think, but about more fundamental things. Inspired by F # th.



Immutability (immutability)


The biggest problem in the world of enterprise development is the struggle with complexity. Code readability is probably the first thing we should try to achieve when writing any more or less complex project. Without this, our ability to understand the code and make reasonable decisions based on this is significantly impaired.

Do mutable objects help us in reading the code? 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 queryObject changed by the time of searching for customers a second time? Maybe yes. Or maybe not. It depends on whether this object was modified by the AdjustSearchCriteria method. To find out, we need to look inside this method, its signature does not give us enough information.

Compare this with 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); } 

In this example, it is clear that AdjustSearchCriteria creates a new criterion object and then uses it for a new search.

So what's the problem with variable data structures?


How to create immutable types


In future versions of C #, the immutable keyword may appear. With its help, it will be possible to understand whether a type is immutable by simply looking at its signature. In the meantime, we have to use what we have.

If you have a relatively simple class, consider making it immutable. This guideline correlates with the concept of Value Objects .

Take, for example, the ProductPile class, which describes a number of products 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 can mark its properties as read-only and add 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; } } 

Now suppose you need to reduce the Amount property by one each time you sell one of the products. Instead of changing the existing object, we can create a new one based on the existing one:

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

What does this give us?


Restrictions


Of course, every good practice has its price. While small classes take full advantage of immutability, such an approach is not always applicable in the case of large types.

In the first place, immutability carries potential performance problems. If the object is quite large, the need to create a copy of it with each change can be a problem.

A good example here will be unchangeable collections . The authors took into account potential performance problems and added a special Builder class that allows you to change the state of collections. After the collection is brought to the required state, it can be finalized by converting it into immutable:

 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 

Conclusion


In most cases, immutable types (especially if they are fairly simple) make the code better.

The remaining articles in the series:



English version of the article: Functional C #: Immutability

Source: https://habr.com/ru/post/266873/


All Articles