NDepend Blog

Improve your .NET code quality with NDepend

Unveiling the Impressive Features of Upcoming C# 12

October 23, 2023 5 minutes read

C# 12 New Features

Microsoft unveils new features in C# 12 Preview. C# 12 along with .NET 8 will be officially released in November 2023. Let’s explore the latest impressive enhancements in this post.

Primary Constructors syntax 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, but it’s important to note that they work differently. Actually the motivation behind their implementation varies:

  • 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 internal logic that relies on internal states, the primary constructor aids in initializing these states during construction.

Primary constructors eliminate the need for declaring private fields and manually linking parameter values to those fields in constructor bodies. Say goodbye to that tedious process:

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 makes 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 I guess 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 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) that 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.