C# 9.0

Tags:

It may be that .NET 5, the one and only .NET that will clear the confusion and lead the way for the next years was probably the biggest(?) announcement of Microsoft Build 2020, but there were numerous other equally important; from the general availability of the Blazor WebAssembly, the Azure Static Web Apps and all the projects related to IoT and Artificial Intelligence, all the way to .NET MAUI (short for Multi-platform App UI), Visual Studio Codespaces,  Entity Framework Core 5, Project Tye, Azure Quantum and the multiple new features and capabilities of Azure Cosmos DB.

Although there were many more interesting things, C# 9 was left out intentionally because in this post we will deal with some of its exciting new features!

init accessor

Up until C# 9, in order to use the object initializer syntax the properties of that object had to be mutable, which means they could change anywhere in the code even after object initialization. In other words, there was no way to use object initilizer on immutable properties, or even better a property had to be publicly accessible to use object initializer:

The init accessor comes to solve this problem, by allowing the object initializer syntax but no field mutation after initialization:

Immutability with readonly

The public string FirstName { get; private set; } in the example is not really immutable because it is allowing changes within the object after object initialization. In C# 8, a truly immutable object that wouldn’t of course allow object initializer syntax would be like this:

The init accessors can be used in similar scenarios where readonly fields are necessary, because init accessors can only be called during initialization and thus they are allowed to mutate readonly fields:

Records

In a nutshell, records are a new lightweight immutable type that affects the immutability of an entire object -not just its properties-, thus making it behave more like a value (it should be seen more as data and less as object):

The data keyword on the class declaration marks it as a record. Although they can have methods, properties or even operations they will still allow structural equality comparisons but not encapsulated state mutation. Instead, on each state change, new records should be created that will reflect this change, and C# 9 has a easy way to do this with the with expression!

with expressions

Immutable objects do not represent state over time (they cannot change!) but state at a specific point in time; a common practice to follow changes over time with immutable objects is to create a copy of the initial object changing only the properties that indeed changed, a process called non-destructive mutation.
The with expression comes to help this coding style, following object initializer syntax:

Altering with behavior with a custom constructor

Under the hoods a protected constructor is implicitly defined and used by with to copy property values from the original object, applying at the same time any changes defined. If the default with behavior is not good enough, a “copy-constructor” can be explicitly defined and this one will be called instead:

Syntactic sugar for Records

Since records are intended to be immutable, the defaults of a simple member declaration has changed:

Going a step further, records are implicitly defining positional constructor and destructor:

Altering the default behavior of the positional constructor/destructor is possible by explicitly defining new ones.

Improved pattern matching

Pattern matching, initially added in C# 7 and improved in C# 8, is a way to test that a value has a certain shape, and extract information from the value when it has the matching shape. Pattern matching play a significant role in producing cleaner code for algorithms that are frequently needed today; for example extracting and consuming information from diverse resources that don’t share a common model and whose model isn’t even part of the original system.

Read a great tutorial about pattern matching in Microsoft Docs.

C# 9 added several new kinds of pattern, for simple, relational and logical patterns. Let’s expand our record above to include age and use it as a example:

Simple type patterns

Currently, a type pattern needs to declare an identifier when the type matches even if that identifier is a discard using _. Well, not any more:

Relational patterns

Patterns that correspond to the relational operators (<, >, <=,>=) are introduced that will contribute to cleaner more readable code. Check how to example above is transformed:

Logical patterns

Finally, logical patterns (and, or, not) are introduced that combine other patterns. They are spelled out as words to avoid confusion with operators that are used within a pattern:

There are more…!

Here is a list of all the features coming to C# 9!

I didn’t write this list on my own, I took it from the Language Feature Status!