This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.

Title

Avoid primary constructors in C# (for now)

Description

<info><b>tl;dr:</b> avoid C# 12's primary constructors for classes except for very small, simple classes, in which case you should consider using a <c>record</c> instead.</info> The following video discusses the downsides of the current implementation of primary constructors: <media href="https://www.youtube.com/watch?v=IABO8O9cZOw" src="https://www.youtube.com/v/IABO8O9cZOw" source="YouTube" width="560px" author="Nick Chapsas" caption="The C# 12 Feature You Shouldn’t Use, Yet"> To sum up: <ul> Primary constructors don't have a <c>readonly</c> 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 </ul> 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 <a href="https://blog.jetbrains.com/dotnet/2023/11/23/primary-constructors-using-csharp-12-in-rider-and-resharper/" author="Matthias Koch" source="JetBrains Blog">Primary Constructors – Using C# 12 in Rider and ReSharper</a> describes another ugly phenomenon: <i>double capture</i>. <bq quote-style="none"> Let’s consider the following example:<code>public class Person(int age) { // initialization public int Age { get; set; } = age; // capture public string Bio => $"My age is {age}!"; }</code>In this class, the parameter <c>age</c> is exposed both through the <c>Age</c> and <c>Bio</c> property. As a result, the object stores the state of <c>age</c> 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:<code>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! // !!!!</code></bq>