Immutable (immutable) set and Immutable set
Immutable set, as the name implies, means that the set cannot be modified. The data items of a set are provided during creation and cannot be changed throughout the lifecycle.
Why use an immutable object? The immutable object has the following advantages:
Immutable objects can be used as constants naturally, because they are inherently immutable. For the use of immutable objects, they are a good technical practice of Defense programming (defensive programming.
The Microsoft. NET team has officially released the immutable set, which can be added through Nuget, including the following immutable set:
System. Collections. Immutable. ImmutableArray
System. Collections. Immutable. ImmutableArray <T>
System. Collections. Immutable. ImmutableDictionary
System. Collections. Immutable. ImmutableDictionary <TKey, TValue>
System. Collections. Immutable. ImmutableHashSet
System. Collections. Immutable. ImmutableHashSet <T>
System. Collections. Immutable. ImmutableList
System. Collections. Immutable. ImmutableList <T>
System. Collections. Immutable. ImmutableQueue
System. Collections. Immutable. ImmutableQueue <T>
System. Collections. Immutable. ImmutableSortedDictionary
System. Collections. Immutable. ImmutableSortedDictionary <TKey, TValue>
System. Collections. Immutable. ImmutableSortedSet
System. Collections. Immutable. ImmutableSortedSet <T>
System. Collections. Immutable. ImmutableStack
System. Collections. Immutable. ImmutableStack <T>
MSDN Documentation Reference https://msdn.microsoft.com/zh-cn/library/system.collections.immutable.aspx, how to use it? Let's look at an example. Suppose you have already created a billing system, and you need an immutable design. In the case of multi-threaded operations, you do not need to worry about data corruption. For example, you need to print a snapshot of data through an auxiliary thread. This method avoids blocking the user's editing operations and allows the user to continue editing without affecting printing.
The variable data model is as follows:
Class Order
{
Public Order ()
{
Lines = new List <OrderLine> ();
}
Public List <OrderLine> Lines {get; private set ;}
}
Class OrderLine
{
Public int Quantity {get; set ;}
Public decimal UnitPrice {get; set ;}
Public float Discount {get; set ;}
Public decimal Total
{
Get
{
Return Quantity * UnitPrice * (decimal) (1.0f-Discount );
}
}
}
Next we will convert it into an immutable design:
Class OrderLine
{
Public OrderLine (int quantity, decimal unitPrice, float discount)
{
Quantity = quantity;
UnitPrice = unitPrice;
Discount = discount;
}
Public int Quantity {get; private set ;}
Public decimal UnitPrice {get; private set ;}
Public float Discount {get; private set ;}
Public decimal Total
{
Get
{
Return Quantity * UnitPrice * (decimal) (1.0f-Discount );
}
}
}
This new design requires you to create an order and create a new instance whenever any attribute value changes. You can add the WithXxx method so that you can update a single attribute without explicitly calling the constructor:
Class OrderLine
{
//...
Public OrderLine WithQuantity (int value)
{
Return value = Quantity
? This
: New OrderLine (value, UnitPrice, Discount );
}
Public OrderLine WithUnitPrice (decimal value)
{
Return value = UnitPrice
? This
: New OrderLine (Quantity, value, Discount );
}
Public OrderLine WithDiscount (float value)
{
Return value = Discount
? This
: New OrderLine (Quantity, UnitPrice, value );
}
}
This makes it easier to use immutable:
OrderLine apple = new OrderLine (quantity: 1, unitPrice: 2.5 m, discount: 0.0f );
OrderLine discountedAppled = apple. WithDiscount (. 3f );
Now let's take a look at how we implement the immutability of orders. The Lines attribute is read-only, but it refers to a mutable object. Because it is a set, it can be easily converted by simply replacing ImmutableList <T>:
Class Order
{
Public Order (IEnumerable <OrderLine> lines)
{
Lines = lines. ToImmutableList ();
}
Public ImmutableList <OrderLine> Lines {get; private set ;}
Public Order WithLines (IEnumerable <OrderLine> value)
{
Return Object. ReferenceEquals (Lines, value)
? This
: New Order (value );
}
}
This design has some interesting attributes:
• This constructor accepts IEnumerable <T> and allows passing in any collection.
• We use the ToImmutableList () Extension Method to convert it to ImmutableList <OrderLine>. If the instance is already an unchangeable list, it will simply convert it instead of creating a new set.
• The WithLines () method complies with our order conventions. If the new list is the same as the current list, you can avoid creating a new instance.
We can also add some convenient methods to make it easier to update the order line:
Class Order
{
//...
Public Order AddLine (OrderLine value)
{
Return WithLines (Lines. Add (value ));
}
Public Order RemoveLine (OrderLine value)
{
Return WithLines (Lines. Remove (value ));
}
Public Order ReplaceLine (OrderLine oldValue, OrderLine newValue)
{
Return oldValue = newValue
? This
: WithLines (Lines. Replace (oldValue, newValue ));
}
}
The code for adding an order looks like this:
OrderLine apple = new OrderLine (quantity: 1, unitPrice: 2.5 m, discount: 0.0f );
Order order = new Order (ImmutableList. Create (apple ));
OrderLine discountedApple = apple. WithDiscount (discount );
Order discountedOrder = order. ReplaceLine (apple, discountedApple );
The benefit of this design is that it avoids unnecessary object creation as much as possible. For example, if the discount value is equal to 0.0 f, there is no immediate discount. discountedApple and discountedOrder reference the existing instance's apple and orders.
This is because:
1. apple. WithDiscount () will return the existing instance of apple, because the new discount is the current value of the same discount attribute.
2. order. ReplaceLine () if both parameters are the same, an existing instance is returned.
Other operations in our unchanged set follow this principle to maximize reuse. For example, adding an order line to an order line of 1000 and an order line of 1,001 do not create an entire new list. Instead, it will reuse a large part of the existing list. This is possible because the internal structure of the list is a tree that allows nodes of different instances to be shared.
There are two sets of videos that describe variability:
Immutable Collections for. NET
Inner workings of immutable collections
Recommended series of blogs with immutable collections:
Indexing ing the. NET CoreFX Part 9: Immutable Collections and the Builder
Ing the. NET CoreFX Part 13: ImmutableList is an AVL Tree
Indexing ing the. NET CoreFX Part 14: Inside Immutable Collections