Subscribe and receive free guide - Ultimate Data Visualization Guide

* indicates required

I love writing about new features of C# and publish an article about it every year. This year is not an exception and I waited for C# 11 with a lot of excitement. However, if there is one thing that I’ve learned from previous years, that is to always wait until the Build event to actually write the article. Every year some features are revoked, I have to update the article more than I’d like to and I need to apologize to people in the comments for publishing the article with non-confirmed features. That almost happened this year with C# 11 as well.

ML.NET Full-Stack: Complete Guide to Machine Learning for .NET Developers

From the basics of machine learning to more complex topics like neural networks, object detection and NLP, this course will guide you into becoming ML.NET superhero.

In the early peak, there was a feature called Parameter Null-Checking. Microsoft claimed that “.NET Runtime removed nearly 20,000 lines of code using this new null-check syntax.” Was I excited about it? Oh boy, that was my first candidate for this year’s article. However, I was patient and waited for the community’s response, just like Microsoft did. So, the feedback was not that good, the feature is revoked. Kudos to .NET team for listening to the community and a treat for me for not jumping the gun 💀 Ok, let’s see some of the most exciting features in C# 11:

  1. Raw String Literal
  2. Generic Attributes
  3. Generic Math Support
  4. Pattern Matching List
  5. Auto-Default Struct

1. Raw String Literal

There are a couple of new features regarding better handling of strings in C# 11. Namely, there is a feature that is providing an option to add a new line in string interpolations. That is pretty cool. Or, there is a feature that is making it easier and less error-prone to create UTF-8 strings. Yap, nice feature as well.

However, Raw String Literal is in my opinion the most important one. It will definitely affect how we write C# code the most. Adding quotes or embedded language strings like JSON, XML, HTML, SQL, Regex and others, into string literals will be much easier thanks to this C# 11 feature. 

If you work with strings literal that contain quotes or embedded language strings like JSON, XML, HTML, SQL, Regex and others, raw literal strings may be your favorite feature of C# 11.
Kathleen Dollard

Principal Program Manager, .NET, Microsoft

Programming Visual

1.1 Current Problem

At the moment, adding quoted text into the string literal can be done using escape char – backslash “\”. You need to do something like this:

var string = "This is a string literal that contains a \"qoute\"."

It has been like this…well, since forever. It is a habit that I am looking to not do again. Raw string literal is a new way of formatting strings that provides a way to do this in a more elegant way.

1.2 C# 11 Feature

C# 11 solves this problem with a new format. This format allows different types of special characters without needing to escape them. Effectively this means that Raw string literals have no escaping. If you add a backslash, the output will be – backslash (duh!). For example, the \n is output as the backslash and a n, not as the new line. Apart from special characters, Raw string literal also allows:

  • Arbitrary text
  • Embedded quotes
  • New lines
  • Whitespaces

To use it, you need to start and end with at least three double quotes ("""..."""). Like this:

Console.WriteLine("""
    This is some "quote".
    Multiple lines and "quotes"? Yeah!
        And "quote" that is indented? Yeah Yeah!
    """);
This is some "quote".
Multiple lines and "quotes"? Yeah!
    And "quote" that is indented? Yeah Yeah!

Note that the previous sentence contains this part – at least three quotes. This means that you can use more! If you use three quotes – “”” your string can contain double quotes – “”. However, if you use four quotes – “”””, your string can contain even triple quotes – “””. Like this:

Console.WriteLine("""
    Double ""quote"" baby!
    """);

Console.WriteLine(""""
    Triple """quote""" baby!
    """");
Double ""quote"" baby!
Triple """quote""" baby!

Another cool thing is that this feature can be combined with string interpolation, so you can use a variable number of $ as well if you want to build more complex strings with nested curly brackets {}. Like this:

var value = 11;
var name = "field_name";

Console.WriteLine($$"""
    Add this to JSON: {{{name}}: {{value}}}
    """);
Add this to JSON: {field_name: 10}

2. Generic Attributes

This new C# 11 feature will improve how we build generic classes, specifically the ones whose base class is System.Attribute and require System.Type as a parameter. It is interesting that the first mentions of these features can be traced back to 2017. So, this feature has been cooking for 5 years. Find out more here.

Candidate for a major C# version, if we can make a sufficient number of runtime versions deal with it.
C# Language Design Notes

Feb 21, 2017

Programming

2.1 Current Problem

So far, this is how we created attributes that require System.Type as a parameter:

public class TypeAttribute : Attribute
{
    public TypeAttribute(Type t) => ParamType = t;
    public Type ParamType { get; } 
}

Then to apply that attribute we would need to use typeof operator. 

[TypeAttribute(typeof(string))]
public string Method() => default;

Bah, that is weird. Clearly, this is a place where generic classes can be used and that is exactly what the new C# 11 feature is providing for us.

2.2 C# 11 Feature

From now on, we will be able to create generic attributes, like this:

public class GenericAttribute<T> : Attribute { }

And use it like this:

[GenericAttribute<string>()]
public string Method() => default;

Note that generic type must be completely constructed, so you can not use some other T instead of string in the code above. Apart from that, the type argument that is passed on, in general, must satisfy the same restrictions as the typeof operator.

Another restriction of this feature is that it doesn’t support types that require metadata annotations. This means that types like dynamic, nullable reference types, tuples, nint/nuint are not allowed. These types include annotations that describe the type, so you need to use the underlying type instead (object instead of dynamic, string instead of string?, etc.)

3. Generic Math Support

.NET 6 contains two features, which are in preview form. The idea with this move was to get feedback from the community as soon as possible and improve on that. Preview features are opt-in because it is expected to change, likely in breaking ways. I think this really cool and that this will be standard practice from now on. According to Microsoft, they enabled a “preview” API mechanism, and this will be the case.

One of those features is Generic Math Support. It seems that the feedback for this feature was good, and this feature will be a part of C# 11 and .NET 7.

It is highly recommended that you try the feature out and provide feedback if there are scenarios or functionality you feel is missing or could otherwise be improved.
Tanner Gooding

Software Developer on the .NET team, Microsoft

Computer

In order to try them, you should configure project file like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <EnablePreviewFeatures>true</EnablePreviewFeatures>
    <LangVersion>preview</LangVersion>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Runtime.Experimental" Version="6.0.0-preview.7.21377.19" />
  </ItemGroup>

</Project>

3.1 Current Problem

Did you have a situation in which you could use operands between generic type variables, but that was just not possible? This is a feature that has been requested for a while by the .NET community, and personally, I had several situations where this could be quite usefull.

3.2 C# 11 Feature

This feature is basically built by creating new static abstract interfaces. These interfaces are built for different operators available to the language. These interfaces are granular and it is possible to extend them or modify them. Here I am using code available at Microsoft because I think it is quite neat and shows the benefits of generic math. It is an implementation of Standard Deviation using generic math:

public static TResult Sum<T, TResult>(IEnumerable<T> values)
    where T : INumber<T>
    where TResult : INumber<TResult>
{
    TResult result = TResult.Zero;

    foreach (var value in values)
    {
        result += TResult.Create(value);
    }

    return result;
}

public static TResult Average<T, TResult>(IEnumerable<T> values)
    where T : INumber<T>
    where TResult : INumber<TResult>
{
    TResult sum = Sum<T, TResult>(values);
    return TResult.Create(sum) / TResult.Create(values.Count());
}

public static TResult StandardDeviation<T, TResult>(IEnumerable<T> values)
    where T : INumber<T>
    where TResult : IFloatingPoint<TResult>
{
    TResult standardDeviation = TResult.Zero;

    if (values.Any())
    {
        TResult average = Average<T, TResult>(values);
        TResult sum = Sum<TResult, TResult>(values.Select((value) => {
            var deviation = TResult.Create(value) - average;
            return deviation * deviation;
        }));
        standardDeviation = TResult.Sqrt(sum / TResult.Create(values.Count() - 1));
    }

    return standardDeviation;
}

4. List Pattern Matching

There is a couple of C# 11 improvements when it comes to pattern matching. For example, in C# 11 you can match Span and ReadOnlySpan with the constant string. This is interesting but in my opinion not as useful as List Patterns. Working on data science projects is getting easier with .NET technologies and this feature is definitely a push in the right direction. It is interesting that a similar feature was added in the previous version of Python

We’re considering adding support for list patterns on IEnumerable types. If you have a chance to play with this feature, let us know your thoughts on it.
Kathleen Dollard

Principal Program Manager, .NET, Microsoft

Computer

4.1 Current Problem

At the moment, we can not use pattern matching for lists and arrays, nor for their slices. For example, we can not do something like this:

int ListPatternMatching(int[] values)
=> values switch
{
    [2, 9, 11] => 1,
    [..] => 5
};

4.2 C# 11 Feature

This new feature allows you to match against lists and arrays. A very cool option is that you can use slice patterns to discard or capture zero or more elements. Let’s start simple and create a method that is trying to check if the array has certain elements. We could do something like this:

int ListPatternMatching(int[] values)
=> values switch
{
    [2, 9] => 1,
    [..] => 5
};

Console.WriteLine(new[] { 2, 9 }); => output: 1
Console.WriteLine(new[] { 33, 66, 111 }); => output: 5

The code from above will return value 1 if we pass on the array [2, 9] and it will return 5 in any other case. But what if we want more. Let’s say we want to check if the array contains three elements and starts with combo 2, 9.

int ListPatternMatching(int[] values)
=> values switch
{
    [2, 9] => 1,
    [2, 9, .., 11] => 2,
    [..] => 5
};

Console.WriteLine(new[] { 2, 9 }); => output: 1
Console.WriteLine(new[] { 2, 9, 11 }); => output: 2
Console.WriteLine(new[] { 2, 9, 11, 33 }); => output: 2
Console.WriteLine(new[] { 33, 66, 111 }); => output: 5

As you can see, both arrays {2, 9, 11} and {2, 9, 11, 33} are satisfying the condition 2. We can also check if the array starts with a certain number and we don’t care about the others or an array that has a certain number of elements, but we care only about the first one.

int ListPatternMatching(int[] values)
=> values switch
{
    [2, 9] => 1,
    [2, 9, .., 11] => 2,
    [2, _] => 3,
    [2, ..] => 4,
    [..] => 5
};

Console.WriteLine(new[] { 2, 9 }); => output: 1
Console.WriteLine(new[] { 2, 9, 11 }); => output: 2
Console.WriteLine(new[] { 2, 9, 11, 33 }); => output: 2
Console.WriteLine(new[] { 2, 33 }); => output: 3
Console.WriteLine(new[] { 2, 33, 111 }); => output: 4
Console.WriteLine(new[] { 33, 66, 111 }); => output: 5

Third condition checks if the array is having two elements and the first one is number 2. This condition is working for the array {2, 33}. Fourth condition checks if the array starts with the number 2 (pun not intended) but it doesn’t care about the length of the array. This condition is working for the array {2, 33, 111}. As you can see you can use [..] as a default pattern.

In order to work with list patterns, the type has to be countable and indexable, basically to have Lenght or Count property and to have an index. Similarly, in order to work with Slice patterns work type has to be countable and sliceable.

In my opinion, this feature is such a nice extension of several C# features that we had to see in the past.

5. Auto-Default Struct

This feature is in this list, simply because I don’t know how it was not a part of C# before. It is a nice touch and displays the maturity and consistency of NET technologies. 

Partial

5.1 Current Problem

Nowadays, you need to initialize all fields of a struct by initializing fields and auto-properties or setting them in the constructors. This is somewhat suboptimal and leads to lower developers’ performance. For example, you can not do something like this in previous versions of C#, because the properties are not initialized:

public readonly struct Structure
{
    public Structure(double value)
    {
        Value = value;
    }

    public Structure(double value, string description)
    {
        Value = value;
        Description = description;
    }

    public Structure(string description)
    {
        Description = description;
    }

    public double Value { get; init; }
    public string Description { get; init; } = "Ordinary Structure";

    public override string ToString() => $"{Value} ({Description})";
}

var structure = new Structure();
Console.WriteLine(structure);

5.2 C# 11 Feature

The code above throws a compiler error. So, the guys at Microsoft said: “Ok, if we have sufficient information to provide the error, perhaps we should just set these values to default!” And that is exactly what the compiler now does – it initializes any fields and auto-properties that are not set based on definite assignment rules, and assigns the default value to them. Seems logical, but it just wasn’t there. This means that this code will have an output:
public readonly struct Structure
{
    public Structure(double value)
    {
        Value = value;
    }

    public Structure(double value, string description)
    {
        Value = value;
        Description = description;
    }

    public Structure(string description)
    {
        Description = description;
    }

    public double Value { get; init; }
    public string Description { get; init; } = "Ordinary Structure";

    public override string ToString() => $"{Value} ({Description})";
}

var structure = new Structure();
Console.WriteLine(structure);
0 ()

Conclusion

In this article, we had a chance to get familiar with 5 new features that the new version of C# will bring. To be honest I can’t wait to try them all out. Some of them seem like a major improvement and it seems that it will affect how we organize and write C# projects.

Thank you for reading!

ML.NET Full-Stack: Complete Guide to Machine Learning for .NET Developers

From the basics of machine learning to more complex topics like neural networks, object detection and NLP, this course will guide you into becoming ML.NET superhero.

Nikola M. Zivkovic

Nikola M. Zivkovic

Nikola is the author of books: Ultimate Guide to Machine Learning and Deep Learning for Programmers. He loves knowledge sharing, and he is an experienced speaker. You can find him speaking at meetups, conferences, and as a guest lecturer at the University of Novi Sad.

Complete Guide to Machine Learning for .NET Developers ML.NET Full-Stack

From the basics of machine learning to more complex topics like neural networks, object detection and NLP, this course will guide you into becoming ML.NET superhero.