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