NDepend Blog

Improve your .NET code quality with NDepend

C# 12 New Features

April 26, 2024 5 minutes read

C# 12 New Features

C# 12 along with .NET 8 has been officially released in November 2023. Let’s explore C# 12 New Features in this post.

Primary Constructors syntax is now usable on class and struct in C# 12

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

C# 12 introduces primary constructors for non-record classes and structs. However, it’s important to understand that the reasons for implementing them vary:

  • In records, the primary constructor offers a succinct method to generate public read-only properties. This is useful for creating simple immutable objects meant to store states.
  • In C# 12 classes and structs, the primary constructor provides a concise way to generate private fields. Since classes and structs often have an internal logic that relies on internal states, the primary constructor aids in initializing these states during construction.

Primary constructors eliminate the need to declare private fields and manually link parameter values to those fields in constructor bodies. Say goodbye to that boilerplate code:

CSharp12 Primary Constructor Motivation

Notice that in non-record classes and structs, properties aren’t automatically generated for primary constructor parameters. You need to create them manually to indicate which data is accessible. This aligns with the typical usage of classes

More about primary constructor can be found in this post: C#12 class and struct Primary Constructors (naming conflict, interaction with other constructors, property, advanced usage of fields generated…).

C# 12 Collection Literals

C# 12 introduces a new terse syntax [a, b, c] to create common collections. The goal is to provide a unified and user-friendly API for declaring collections. In the code sample below notice the usage of the spread operator .. that let’s concatenate the collections:

A similar syntax using braces {a, b, c} is available for a long time, but it is limited to working with arrays

C# array brace collection

This syntax also works to declare jagged arrays but not multi-dimensional arrays:

C# jagged array

It is easy to make this new syntax usable with your collections classes thanks to the new attribute [CollectionBuilder] :

Notice that new syntax for dictionary literals has been mentioned in the design discussions but this has been postponed for at best C# 13:

Also, a collection literal expression cannot be used directly as the first parameter for an extension method invocation. This is because no natural type gets inferred from a collection literal expression but this might be improved in future C# versions:

C# collection literal natural type

C# 12 Using directives to alias any types

C# has long-supported aliases for namespaces and named types (classes, delegates, interfaces, records, and structs). This feature effectively resolves naming conflicts when using directives may introduce ambiguity. Additionally, it simplifies the usage of complex generic types by providing concise alternatives. However, the language’s growing collection of complex type symbols has created a demand for aliases that are currently not permitted. Consider tuples, nullable and function-pointers, which often have lengthy and intricate textual representations that are cumbersome to write repeatedly and challenging to read. By allowing more aliases in C# 12, developers can assign shorter names to replace these extensive structural forms, thereby enhancing code readability and developer productivity.

C# 12 Default values for lambda expressions

C# 12 boosts lambda expressions by enabling developers to set default parameter values. The syntax resembles that of other default parameters. For instance,  (int val = 10) => val + 1; . The default value 10 is utilized when no value is provided in the lambda call.

C# 12 nameof operator improvement

The nameof operator now works in C# 12 with member names, including static members, initializers, and in attributes:

C# 12 Inline arrays

The InlineArrayAttribute has been introduced with .NET 8 and can be used from C# 12 programs. The principal purpose of this attribute is to designate a type as eligible for representation as a contiguous sequence of primitives. The size of an inline array is fixed at compile time. This makes it faster because the exact layout is known by the compiler that generates optimized IL code to access elements of the inline array. Also, an inline array is a struct which makes it allocated on the thread stack and not on the heap as with regular .NET arrays. Here is a short program based on .NET 8 inline arrays, notice how inline array element access is similar to regular array element access through inlineArray[i] :

Clearly, this C# 12 new feature is a niche one. It is chiefly aimed at utilization by the compiler, the .NET BCL, and select third-party libraries.  But for those that need more performance like in the game industry, inline arrays are welcome.

C# 12 Interceptors

C# 12 interceptors is a powerful – yet strange – new feature that we are going to demonstrate first. Then we’ll go through potential use cases. To make work this short program you need a C# 12 compatible project with 2 files, Program.cs and MyInterceptors.cs. The .csproj project file content must look like this:

Here is the content of Program.cs:

Here is the content of MyInterceptors.cs:

This program outputs:

If we decompile the assembly generated, it with ILSpy we can see that calls to Method() have been hijacked by the compiler:

Notice that if you change anything in:

System.Runtime.CompilerServices.InterceptsLocationAttribute(@"C:\Users\pat\source\repos\ConsoleApp170\ConsoleApp170\Program.cs", 3,5)]

the compiler emits an error because online a valid triple of (absolute file path + line + column) can work.

Why are they planning such a weird feature in C# 12? The primary rationale is Ahead-Of-Time compilation (AOT) which becomes first citizen with .NET 8. While interceptors aren’t exclusively tailored to AOT, their design unmistakably takes AOT into account. Through the employment of interceptors, it becomes feasible to transform code that previously posed challenges for AOT into versions generated directly from the source.

The GeneratedCode class will be generated by a tool and developer won’t have to even change their code.

Conclusion

This article delves into new features introduced in C# 12 Preview. Microsoft is dedicated to enhancing the C# language, prioritizing efficiency and developer-friendliness. Expect further advancements in the near future.