C# records immutability

This commit is contained in:
2021-07-30 17:17:45 +03:00
parent 52dae87a2d
commit 52dec9b278
2 changed files with 72 additions and 1 deletions

View File

@@ -1,2 +1,5 @@
# pages # Some thoughts on programming
## [C# records and immutability](./RecordImmutability/README.md)
Possibly unwanted side effects with records.

View File

@@ -0,0 +1,68 @@
# Shallow copy
When C# records were intrduced, thy were created with immutability in mind.
This introduced `with` keyword.
``` cs
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:
``` cs
Point point = new Point(0, 0);
Point point2 = point.<Clone>$();
point2.X = 1;
Point point3 = point2;
```
`<Clone>$()` is automatically generated method of `Point`.
``` cs
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](https://sharplab.io/#v2:CYLg1APgbghgTgAgC4HsAOAZApgMyQgXgQDssB3BABRQEtikAKABgBoEmBKAbgFgAoWIhjAAVjADGWeoWTpseBGRpIAFggDeCABoyAjAgC+vPvwACAZgRws4lHGBVa9BnXxa2rhAE1uQA===).
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 `Point`s will illustrate this:
``` cs
public record Path(Point[] Points);
```
Here `Points` can be shared between "immutable" instances of `Path`. Modifying n<sup>th</sup> element of the array will affect both instances.
# Enter immutability
``` cs
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](https://sharplab.io/#v2:CYLg1APgAgDABFAjAOgMIHsA2mCmBjAFwEt0A7AZ2QEkBbGgVwIEMAjXAbgFgAoHgNyYAnOAXQAHADI4AZgTgBeOKRwB3OAAV0RUgQAUMADRwYASi7cBwpsABWTPDh0KR4qbLgqiBABZwA3nAAGs6IcAC+5vxCcGJMPs7Kaupx3rq0DMxsOACCgoJMAJ5ogjhxOLqikjIERtZ2DjomZlHCeJjo5DjAyfGKsfGe8QGa2gTkzv3eyCM6lNnAwBWu1SYRPDxQAMxwJXjogsAaWjq6o0FGZwCazdxbO/j7hz2p6YysuLn5BQA8MwQAfEdRuQzEA=).
`System.Collections.Immutable` solves all that and even with shallow copy, modifications will not be shared.