.NET 8 was released on November 14 2023 and with this release, we also get access to C# 12. C# 12 has 6 big feature updates that as a .NET developer, you need to know about.

Within this article, you will learn all about each feature and get access to the code required to implement each one. Read on, if you want to master the latest version in .NET in under 10 minutes, ๐Ÿ”ฅ๐Ÿ”ฅ๐Ÿ”ฅ

First, in order to get going with C# 12, you will need to install the .NET 8 runtime as well as update your Visual Studio 2022 instance to version the latest version. You can download the .NET framework from the MSDN site here.

Collection Expressions

One thing that has always bugged me over the years when working with .NET is arrays, why? For the most part, I tend to work with Lists and Enumerables, and on the odd occasion that I need to use an array the initialization syntax is always slightly different. This means that I can never remember the convention I need to use straight away and I have to hack my code until I get it right

Now I'm not saying that this is an insurmountable issue. This is definitely not on the same level as trying to figure out how an undocumented API works, however, it has frequently brought me out of the flow of coding!

The first feature that we are reviewing, collection expressions, will unify the code initialization code required to create arrays, spans, read-only spans, and lists. Collection expressions will also make it easier to combine data stored in different types into a single type! Historically, you might define an array using one of these three techniques:

Instead of writing code like this, the collection expression syntax will simplify things:

In the first line of the snippet above, you can see how the array initialization syntax is simplified. Instead of using {} you use [] and drop the new operator.

As we are talking about a Microsoft-supported language you know that with their love of backward compatibility, the old array creation syntaxes still work. In essence, there are now 4 ways of doing the same thing in .NET 8 which at first glance looks like it adds more complexity rather than eliminates it.

The real benefit of adding a new way of doing things is that this new syntax works exactly the same with all the other collection types. As you can see in the snippet below when you use it everywhere it makes the end code much easier to read:

Another benefit of using this unified approach is that you can also start to combine different collection types together using the spread operator ... easily. This way of working is very common in Javascript. Using a spread, it is now possible to combine lists, using syntax like this:

I personally prefer this syntax compared to being forced into using an extension method which is harder to unit test. More information on this capability can be found here.

Optional parameters in lambda expressions

This feature is pretty simple to master. From C# 12 onwards, it is possible to add default values to initialization parameters that you define within lambda expressions. The framework has allowed developers to add default values for regular method parameters for ages and the good news is that lambdas have joined the party as well:

When this code is first called, by default 'ee' will be assigned to the string and the value 1 will be assigned to the int. More information on this specification change can be found here

Alias any type

Relax the usingaliasdirective (ยง13.5.2) to allow it to point at any sort of type, not just named types. This would support types not allowed today, like tuple types, pointer types, array types, etc. For example, this would now be allowed:

Inline arrays

The inline array feature is the strangest feature in this list. The reason why I say strange is that you will likely never need to implement it, however, your code will likely benefit from them moving forward! The reason for this is that inline arrays are more likely to be used by the .NET core team as they do not add any new features to a normal array. The only difference is how they are called under the hood. This is an example of how you would create an inline array in code:

If we compare calling a normal array with an inline arry the code look like this:

Both of these instructions will allow you to assign 5 values to the array and they will both return the same data, the only difference is where the underlying data is assigned. The inline array uses a struct to store the array data. A struct is a value type, meaning it's allocated either on the stack or inline in containing types. The classi array will be stored as reference types which will be allocated on the heap and garbage-collected.

So in essence, inline arrays will be quicker and take up less memory. I would guess Microsoft will start using inline arrays within the core, so you will get the performance benefits when you use arrays in the future without needing to do much yourself. Unless your application performance bottleneck is with the framework storing arrays, I'm not sure you will ever look at this feature again ๐Ÿ˜•

Primary Constructors

Within any object-oriented language, the class is the main tool you will use to model things within your solution. A class can have methods, properties, as well as a constructor to define the initial state of that class when it is initialized. .NET 8 changes the game when it comes to class creation...

The final feature we are reviewing is primary constructors. The new primary constructor syntax should cut down on the amount of boilerplate code that you need to write whenever you are defining a new class. Instead of having to write a separate function to handle the constructor, in the new world, you can specify the class's constructor arguments at the same time that you define the class. Previously, when you needed to create a class that took some constructor parameters you would write some code like this:

Refactoring the snippet above to use a primary constructor instead would look like this:

This new approach requires you to write less set-up code which in turn should make your code easier to read. It's worth mentioning that when properties are set, they are class-scoped, so you can then use them in exactly the same way as you used to.

This feature kind of borrows from records. When defining a record, you could do something kind of similar, however, the difference with the record pattern is that the passed-in values would be made public, while in primary constructors arguments are privately scoped!

There are a few caveats or things to consider when using this new syntax, constructor inheritance and adding additional logic within the constructor itself being the main two, take this example:

The limitation here is that you can not access the primary constructor value within the context of the classic constructor. Within this introduction to C# 12, I will not cover this topic in detail, however, you can learn more about these nuances in here.

If you simply need to pass arguments in and read them within your class, primary constructors are perfect ๐Ÿ˜Š


Happy Coding ๐Ÿค˜