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:
To sum up:
- Primary constructors don’t have a
readonly
backing 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}!"; }
age
is exposed both through theAge
andBio
property. As a result, the object stores the state ofage
twice! 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! // !!!!