|<<>>|2 of 275 Show listMobile Mode

Avoid primary constructors in C# (for now)

Published by marco on

Updated by marco on

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 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:

public class Person(int age)
{
    // initialization
    public int Age { get; set; } = age;
    // capture
    public string Bio => $"My age is {age}!";
}
In this class, the parameter age is exposed both through the Age and Bio property. As a result, the object stores the state of age 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! // !!!!