C# records immutability
This commit is contained in:
@@ -1,2 +1,5 @@
|
|||||||
# pages
|
# Some thoughts on programming
|
||||||
|
|
||||||
|
## [C# records and immutability](./RecordImmutability/README.md)
|
||||||
|
|
||||||
|
Possibly unwanted side effects with records.
|
||||||
|
|||||||
68
RecordImmutability/README.md
Normal file
68
RecordImmutability/README.md
Normal 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.
|
||||||
Reference in New Issue
Block a user