Avoid primary constructors in C# (for now)
tl;dr: avoid C# 12's primary constructors for classes except for very small, simple classes, in which case you should consider using a
record instead.The following video discusses the downsides of the current implementation of primary constructors:
The C# 12 Feature You Shouldn’t Use, Yet by Nick Chapsas (YouTube)
To sum up:
- Primary constructors don’t have a
readonlybacking field; you can still assign to it within the type. - You can’t control the visibility of the generated property or backing field.
- You can’t throw exceptions, except in a field-initializer, which isn’t as obvious or clean as doing so from within a standard constructor
Nick contrasts the C# implementation with the language feature in Kotlin, which allows all modifiers in the declaration, but has the same problem that the class definition can get pretty wordy.
The article Primary Constructors – Using C# 12 in Rider and ReSharper by Matthias Koch (JetBrains Blog) describes another ugly phenomenon: double capture.
Let’s consider the following example:
In this class, the parameterpublic class Person(int age) { // initialization public int Age { get; set; } = age; // capture public string Bio => $"My age is {age}!"; }ageis exposed both through theAgeandBioproperty. As a result, the object stores the state ofagetwice! For reference types, a double capture leads to an increased memory footprint and possibly even memory leaks. In our concrete example, you will observe the following unintended behavior:var p = new Person(42); p.Age.Dump(); // Output: 42 p.Bio.Dump(); // Output: My age is 42! p.Age++; p.Age.Dump(); // Output: 43 p.Bio.Dump(); // Output: My age is 42! // !!!!