2.6 KiB
Shallow copy
When C# records were intrduced, thy were created with immutability in mind.
This introduced with keyword.
var topLeft = new Point(0, 0);
var adjacent = topLeft with { X = 1 };
public record Point(int X, int Y);
Even though from developer's point of view Point is immutable, that is not the case for compliler. Here is excerpt from code:
Point point = new Point(0, 0);
Point point2 = point.<Clone>$();
point2.X = 1;
Point point3 = point2;
<Clone>$() is automatically generated method of Point.
public virtual Point <Clone>$()
{
return new Point(this);
}
protected Point(Point original)
{
<X>k__BackingField = original.<X>k__BackingField;
<Y>k__BackingField = original.<Y>k__BackingField;
}
You can see full decompiled code here.
While this code looks like it can be optimized further (overwriting X and entire existence of point2 can be completely skipped if only contructor and decomposition is used), it is necessary in case Point is being inherited. Generational garbage collector of .net is quite efficient with such short-lived objects.
This approach has an obvious disadvantage in terms of immutability since <Clone>$() is making shallow copy of the object. Having an example Path that consists of series of Points will illustrate this:
public record Path(Point[] Points);
Here Points can be shared between "immutable" instances of Path. Modifying nth element of the array will affect both instances.
Enter immutability
using System.Collections.Immutable;
var topLeft = new Point(0, 0);
var adjacent = topLeft with { X = 1 };
var path = new Path(ImmutableArray.Create(topLeft, adjacent));
var closedPath = path with { Points = path.Points.Add(topLeft)};
public record Point(int X, int Y);
public record Path(ImmutableArray<Point> Points);
You can see full decompiled code here.
System.Collections.Immutable solves all that and even with shallow copy, modifications will not be shared.