Microsoft shipped out C# 9.0 as a part of the .NET 5 development platform release. .NET 5 is paired with C# 9.0, which brings many new features to the language. The new language features include records, init-only setters, top-level statements, pattern matching enhancements, target-typed new expressions, covariant returns and much more!
A big focus of C# 9.0 is an immutable representation of data shapes, and that is mainly represented with new record types and init-only properties. Usually, records are tagged as one of the most interesting new features in C# 9.0 version.
The record is a new immutable reference type in C#. The result of assigning them or changing the values is a creation of the copy of that object. This is the same way .NET treats the value types and the strings. Another immutable feature of the release are init-only properties, more precisely, init
accessor, a variant of the set
accessor which can only be called during object initialization for a particular property.
The biggest distinction from classes is that record types use value-based equality and they are immutable by default. Using them the whole immutable object is created, not just individual properties of it. Usually, to create immutable reference types, the C# forces you to write a bit of extra code.
There are a couple of advantages of records usage. They have more sense in a concurrent program with shared data. For those particular cases, the answer is the usage of records. They are the most elegant and useful feature to achieve immutability of your types in C#.
To create the record type, the record keyword needs to be used. In the example below, we can see the most simple example of creating a record type of a Person.
The Person
record type below contains two read-only properties: FirstName and LastName. This is an immutable reference type and none of the properties can be modified once it's been created.
public record Person
{
public string LastName { get; }
public string FirstName { get; }
public Person(string first, string last) => (FirstName, LastName) = (first, last);
}
To create new value and represent a new state, C# 9.0 brings with-expressions support. The with-expression provides a copy of its record operand with the specified properties and fields which are modified. It is required that these properties have init or set accessor defined.
var person = new Person { FirstName = "Almir", LastName = "Vuk" };
var otherPerson = person with { LastName = "Casals" };
The complete specification for records immutable reference types can be found here.
Init-only setters are a new feature which can be declared in any type you want, not only in records. Init-only setters provide a nice approach to initialize an object without having to write a bunch of boilerplate code. They can be used for highlighting the property initializer syntax to set these values in creation expressions, but those properties are read-only once construction has completed. This can result in having a code which is easier to maintain and more readable. A common use-case scenario is for data transfer objects.The following example is contained in Microsoft's official documentation. It shows the struct named WeatherObservation
which has three properties with an init setter, and they are easily accessed by the ToString
method.
public struct WeatherObservation
{
public DateTime RecordedAt { get; init; }
public decimal TemperatureInCelsius { get; init; }
public decimal PressureInMillibars { get; init; }
public override string ToString() =>
$"At {RecordedAt:h:mm tt} on {RecordedAt:M/d/yyyy}: " +
$"Temp = {TemperatureInCelsius}, with {PressureInMillibars} pressure";
}
More about init only setters can be found in the official language specification.
While writing simple programs and code files with C# it produces a lot of boilerplate code. Sometimes this can be overwhelming for beginners as it clutters up the code and adds levels of indentation to it. With C# 9.0 support of top-level statements, a simple hello world example looks like this:
using System;
Console.WriteLine("Hello World!");
It is important to notice that only one file in the project can use top-level statements, otherwise, the compiler will report an error. The error will be reported also if there is a combination of top-level statements with a declared program entry point method, like the Main method. Only one file can contain the statements and that would normally be in the Main method of a Program class.
As it is stated on the Microsoft documentation, "One of the most common uses for this feature is creating teaching materials. Beginner C# developers can write the canonical 'Hello World!' in one or two lines of code. None of the extra ceremony is needed. However, seasoned developers will find many uses for this feature as well. Top-level statements enable a script-like experience for experimentation similar to what Jupyter notebooks provide. Top-level statements are great for small console programs and utilities. Azure Functions are an ideal use case for top-level statements."
With C# 9.0 you can also leave out the type on the right side, in case there is a clear type that the expression is being assigned to. For example, when there is a lot of repetition, like in an array or object initialization.
Point p = new (3, 5);
Point[] ps = { new (1, 2), new (5, 2), new (5, -3), new (1, -3) };
The details about target-typed new expression are in the official language specification.
The minor but still important feature of this release is that local function declarations are now permitted to have attributes. Using them, developers can make the coder cleaner and easier to read. David Fowler, software architect at Microsoft, already found a nice usage for it. In this Twitter post, he used the attribute for a local function inside of UseEndpoints
middleware setup. There was quite an amount of comments and feedback about this feature exchanged there. More about the feature can be found in the official language specification.
C# 9.0 also brings new pattern matching improvements, more about pattern matching improvements can be found in the official language specification.
The full specification proposal for C# 9.0 is available. Also, the technical session presented at .NET Conf 2020 highlighting the new language features can be found on YouTube. C# 9.0 is included in the latest Visual Studio 2019 versions.