NDepend

Improve your .NET code quality with NDepend

C#12 class and struct Primary Constructors

C# 12 class and struct Primary Constructor

Since C#9 we have the convenient primary constructor syntax for class record (or just record) and struct record:

C#12 introduces primary constructor for non-record class and struct but beware, it is very different! This is because the underlying motivation is different:

  • record primary constructor represents a concise way to generate public read-only properties. This is because a record is a simple immutable object designed to hold some states.
  • class and struct primary constructor represents a concise way to generate private fields. This is because class and struct are implementations with internal logic that uses internal states often initialized at construction time.

For example here is a situation where quite a few lines of code can be saved:

CSharp12 Primary Constructor Motivation

A valid criticism to consider is that the absence of a primary constructor body could result in the creation of classes that lack proper parameter validation, thereby encouraging the development of lazy code. This potential issue is already present in records’ primary constructors.

No Property Generated

Let’s make clear that unlike record primary constructor, properties are not generated:

CSharp12 Primary Constructor Property Are Not Generated

Since no properties are generated, another difference with record  primary constructor is that the with keyword cannot be used:

CSharp12 Primary Constructor and with syntax

Usage of private fields generated by Primary Constructor

Let’s illustrate how generated private fields can be used:

Actually in this code sample above, the C# compiler generates 2 private fields, one for the primary constructor parameter yearOfBirth and one for the property YearOfBirth1 backing fields:

CSharp12 Primary Constructor Field Decompiled

Naming Conflict

Primary constructor parameter naming conflict is authorized by the compiler with both field and property. This can lead to odd situations like here:

CSharp12 Primary Constructor Field Naming Conflict

The property or field then hides the primary constructor parameter everywhere except in property initialization:

C# 12 Primary Constructor Property hides Parameter

However parameters should be Camel case (yearOfBirth), property should be Pascal case (YearOfBirth) and by convention private fields should be underscore Camel case (_yearOfBirth) so naming conflict should be naturally avoided.

Primary Constructor and Others Constructors

class and struct primary constructors can save quite some code but the downside is that it forces (eventual) other constructors to call the primary constructor through the keyword this :

Also a derived class cannot have a primary constructor as long as its base class doesn’t have a constructor with no parameters (which is named a default constructor):

CSharp12 Primary Constructor and Derived Class

Finally let’s notice that a struct with a primary constructor still has a default constructor.

This is because in C# a struct has value-type semantic. This implies that a default(TStruct) instance can be created through new TStruct() with all states initialized with their default values, 0 for value types and null for references.

Conclusion

C#12 class and struct primary constructor is a great new syntax to write more concise code. However it can be misleading since it is quite different than the existing record primary constructor syntax. This is why in this post we insisted on the different underlying motivations.


I wish a breakpoint could be set on class and struct primary constructors. For now I can only break on primary constructor call when a parameter is used in a property initialization. (I checked that record primary constructors don’t have that also). This could be useful the same way one can break upon a get; or set; expression. I submitted a request for that.

C# 12 class and struct Primary Constructor and breakpoint

 

My dad being an early programmer in the 70's, I have been fortunate to switch from playing with Lego, to program my own micro-games, when I was still a kid. Since then I never stop programming.

I graduated in Mathematics and Software engineering. After a decade of C++ programming and consultancy, I got interested in the brand new .NET platform in 2002. I had the chance to write the best-seller book (in French) on .NET and C#, published by O'Reilly and also did manage some academic and professional courses on the platform and C#.

Over my consulting years I built an expertise about the architecture, the evolution and the maintenance challenges of large & complex real-world applications. It seemed like the spaghetti & entangled monolithic legacy concerned every sufficiently large team. As a consequence, I got interested in static code analysis and started the project NDepend in 2004.

Nowadays NDepend is a full-fledged Independent Software Vendor (ISV). With more than 12.000 client companies, including many of the Fortune 500 ones, NDepend offers deeper insight and full control on their application to a wide range of professional users around the world.

I live with my wife and our twin kids Léna and Paul in the beautiful island of Mauritius in the Indian Ocean.