From 52dec9b278867239dad2e14bcd1e9847169c6888 Mon Sep 17 00:00:00 2001 From: Kiril Markov Date: Fri, 30 Jul 2021 17:17:45 +0300 Subject: [PATCH] C# records immutability --- README.md | 5 ++- RecordImmutability/README.md | 68 ++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 RecordImmutability/README.md diff --git a/README.md b/README.md index 1dda9bb..f2e4a93 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ -# pages +# Some thoughts on programming +## [C# records and immutability](./RecordImmutability/README.md) + +Possibly unwanted side effects with records. diff --git a/RecordImmutability/README.md b/RecordImmutability/README.md new file mode 100644 index 0000000..28a3735 --- /dev/null +++ b/RecordImmutability/README.md @@ -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.$(); + point2.X = 1; + Point point3 = point2; +``` + +`$()` is automatically generated method of `Point`. + + +``` cs + public virtual Point $() + { + return new Point(this); + } + + protected Point(Point original) + { + k__BackingField = original.k__BackingField; + k__BackingField = original.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 `$()` 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 nth 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 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. \ No newline at end of file