Primary constructors are more than syntactic sugar
Switching from a regular constructor to a primary constructor can change when fields get their values. The difference matters when a base constructor calls a virtual member. Steven Giesel shows the difference by looking at the code the compiler generates.
Key points
- In a primary constructor, the parameter is copied into a hidden field before the base constructor call.
- In a classic constructor, the field is assigned only after the base constructor returns.
- If the base constructor calls a virtual member, the classic derived field can still read null.
- The compiler-generated field is not readonly, so it can be reassigned inside the class.
- Mixing the parameter in a member and a field initializer creates two copies and triggers CS9124.
A primary constructor looks like a shorter way to write a regular constructor, but it can change the order in which state becomes available. The author compares two derived classes that pass a closure to a base constructor, and shows that a base constructor calling a virtual member sees a null value in the classic version and the real value in the primary version. The reason is in the generated code: the primary constructor copies the parameter into a hidden field before the base constructor runs, while the classic version assigns its field only afterwards.
This ordering detail matters in real libraries, where timers or other callbacks may read virtual members before the derived constructor body finishes. The generated field is not readonly, so reassignment is still possible. If you need a readonly guarantee, a field initializer also runs before the base constructor, but mixing the parameter in members and initializers produces a duplicate copy and the CS9124 warning.
Real-world use
- Building a library whose base class starts a timer that later reads a virtual property
- Designing a class hierarchy where derived classes pass parameters into base behavior that runs during construction
Why it matters
Before converting a regular constructor to a primary constructor, check whether the base class calls virtual members during construction, since initialization order can change the observed values.
Related stories
- Collection Return Types in C#: Interfaces, Views, and SnapshotsNick Cosentino (Dev Leader)2 d ago
- Dynamic Gauss-Legendre numerical integration in C#: computing nodes and weights on the fly without overflowHabr C# (ru)2 d ago
- Events and Delegates in C#: Contracts, Subscriptions and NotificationNick Cosentino (Dev Leader)3 d ago