In November 2022, the latest versions of .NET 7 and C# 11 were released. There are many new features across the board in .NET, but here we’ll focus on the most useful innovations in the C# language itself.
12-04-2023
In November 2022, the latest versions of .NET 7 and C# 11 were released. There are many new features across the board in .NET, but here we’ll focus on the most useful innovations in the C# language itself.
Since version 8, new releases of C# have been coming out regularly each autumn, meaning that changes tend to be incremental rather than revolutionary. Often a feature is introduced which, though functional, is clearly incomplete and it’s not until a later release that the final elements come into place.
A good example of this is pattern matching. Every release since C# 7 has enhanced this area of the language, and that’s true of C# 11. There’s still more to come, but as we’ll see the introduction of list patterns gives a huge boost to the power of the language.
And there are other improvements too, all making the language more expressive and less error prone. Let’s take a look.
Required Properties
Since C# 9, we’ve had init-only properties:
class Person
{
public int Age { get; init; }
}
This means the property can only be set during initialization, not later:
var p = new Person { Age = 21 };
p.Age = 50; // Compilation Error
But there is no obligation to initialize the property at all. The required keyword fixes that:
class Person
{
public required int Age { get; init; }
}
var p = new Person (); // Compilation Error: Age not set
This can be useful in many situations, but also solves a specific problem with non-nullable reference types, which came in in C# 8:
class Person
{
public string Name { get; set; } // Warning: Name is non-nullable,
// but is default-initialized to null
}
Making the property required means the compiler knows it must be initialized to a non-null value when an object is created:
class Person
{
public required string Name { get; set; } // No Warning
}
Generic Maths and Static Interface Methods
Since the very beginning, C# has allowed operator overloading, which means user-defined types can be manipulated with symbolic operators. This can be particularly useful for mathematical types:
var c1 = new Complex(2, 3);
var c2 = new Complex(2, 3);
var c3 = c1 + c2; // Adding two complex numbers
The problem is that operators are static, and therefore (until now) could not be included in an interface. Thus, it was impossible to write generic code that would operate on any
type that had, say, a + operator. A method would have to be hand-crafted for each type.
Now operators (and other static members) can be defined in interfaces, and there are standard interfaces for each of the mathematical operations, e.g. IAdditionOperators, IMultiplyOperators. Furthermore, there is an interface that combines all the arithmetic operations: INumberBase.
And so now, we can write, for example, a Sum method that works for any type which supports addition:
T Sum<T>(IEnumerable<T> list) where T : IAdditionOperators<T, T, T>, new()
{
var sum = new T();
foreach (var val in list)
sum += val;
return sum;
This will add up complex numbers, or integers, or anything that implements the interface:
Console.WriteLine(Sum (new Complex[] { c1, c2, c3 }));
Console.WriteLine(Sum (new int[] { 2, 3, 5, 7, 11 }));
Raw String Literals
In any language, there is a problem with string literals. How do we express a quotation mark in a string when quotation marks define the string? From the beginning in C#, we’ve been able to use an escape sequence:
var s = "We can \"put\" a word in quotes.";
We can "put" a word in quotes.
The output is fine, but the code itself is hard to read, especially when things get complex. Moreover, it’s tricky to put a line break inside a string.
The C# 11 solution is raw string literals, which we enclose in three (or more) double quotes:
var s = """
We can "put" a word in quotes.
And we can have a line break.
""";
We can "put" a word in quotes.
And we can have a line break.
There’s a neat trick here. The output text is indented four characters, even though in the code it’s at column 13. The indent is calculated not from column one, but from the beginning of the closing quotation marks, which gives us complete flexibility.
But what if we need multiple double quotes within our string? It’s easy. We simply enclose the string with one more set of quotes than we need inside it:
var s = """""""
Here are six double quotes in a row: """"""
""""""";
Here are six double quotes in a row: """"""
String interpolation works similarly. If we want to literally show curly braces, and also use them for interpolation, we just use multiple dollar symbols. That number of curly braces means interpolation – anything else is taken literally. Here, two dollars mean two curly braces for interpolation:
var s = $$"""
These are literal curly braces: {}.
And this is interpolation: {{3 + 4}}.
""";
These are literal curly braces: {}.
And this is interpolation: 7.
List Pattern Matching
Pattern matching has been evolving since it was first introduced in C# 7. The latest feature is list patterns.
Suppose we have some data in a CSV file like this:
Each row has a title, an author, a year of publication, an arbitrary number of ratings, and finally a genre. Because we can’t be sure in advance how many ratings there are, this can be difficult to process, but list pattern matching makes it easy. We could load each row as an array of strings and then extract information about just the classics.
var reaction = row switch
{
[var title, var author, .. , "Classic"] => $"{title} is by {author}.",
_ => "Not a classic."
};
This will exactly match anything with a final element of "Classic" and extract the first two elements into the variables title and author, which are then used in the interpolated string. The .. represents an arbitrary number of items (the year and the ratings) and can be used at most once in any pattern.
Other features include the fact we can also extract the .. into a variable (an array) and any elements we are not interested in can be discarded with a _.
Thus, we could determine the average rating of each book:
var reaction = row switch
{
[_, _, _, _] => "No ratings",
[var title, _, _, .. var ratings, _] =>
$"'{title}': average rating of {ratings.Select(int.Parse).Average()}."
};
The first clause checks for rows which have exactly four entries (title, author, year and genre) and therefore has no ratings. The second extracts the title, discards the author and year, extracts the ratings and finally discards the genre. The title and ratings are then used to generate a message.
But also, there were some features originally proposed for C# 11 that didn’t make the cut. Two of the most anticipated were discriminated unions and semi-auto properties. But it would seem these have not been abandoned entirely, merely postponed until a later version.
Roll on C# 12!
Would you like to know more?
If you found this article interesting you might be interested in some of our related training courses:
To help raise awareness of challenges and vulnerabilities and ways to reduce risk, we've got a bumper crop of cyber security blog articles. We've also got a robust range of hands-on training courses covering security for non-technical staff and IT professionals
We use cookies on our website to provide you with the best user experience. If you're happy with this please continue to use the site as normal. For more information please see our Privacy Policy.