November 8th, 2022

Welcome to C# 11

Mads Torgersen
C# Lead Designer

I am excited to announce that C# 11 is out! As always, C# opens some entirely new fronts, even while advancing several themes that have been in motion over past releases. There are many features and many details, which are beautifully covered under What’s new in C# 11 on our docs pages. What follows here is an appetizer of some of the highlights – small and big.

Before we dive in, let me just say how happy I am about the way this version of C# came into being! With every release there’s more and more participation from the community, contributing everything from suggestions, insights and bug reports all the way up to entire feature implementations. This is really everyone’s C#. Thank you!

UTF-8 string literals

By default C# strings are hardcoded to UTF-16, whereas the prevailing string encoding on the internet is UTF-8. To minimize the hassle and performance overhead of converting, you can now simply append a u8 suffix to your string literals to get them in UTF-8 right away:

var u8 = "This is a UTF-8 string!"u8;

UTF-8 string literals simply give you back a chunk of bytes – in the form of a ReadOnlySpan<byte>. For the scenarios where it’s important to have a UTF-8 encoding this is probably more useful than some dedicated new UTF-8 string type.

Read the docs on UTF-8 string literals.

Raw string literals

A lot of what gets put in string literals is “code” of some sort – not just program text, but also JSON and XML data, HTML, regular expressions, SQL queries, etc. It’s really unhelpful when many of the special characters that show up in such text have special meaning in C# string literals! Noteworthy examples include \ and ", joined in interpolated strings by { and }. Having to escape all of those is a real bummer, and an ongoing source of pain an bugs.

Why not have a form of string literals that has no escape characters at all? That’s what raw string literals are. Everything is content!

A raw string literal is delimited by at least three double-quotes:

var raw1 = """This\is\all "content"!""";
Console.WriteLine(raw1);

This prints:

This\is\all "content"!

If you need three or more "s to be part of your content, you just use more "s on the outside. The beginning and end just have to match:

var raw2 = """""I can do ", "", """ or even """" double quotes!""""";

This makes it really easy to paste in, maintain and read at a glance what the literal contains.

Multi-line raw string literals can also truncate leading white space: The position of the end quote determines where white space starts to be included in the output:

var raw3 = """
    <element attr="content">
      <body>
        This line is indented by 4 spaces.
      </body>
    </element>
    """;
//  ^white space left of here is removed

Since there are four spaces to the left of the end quote, four spaces will be removed from the beginning of every line of content, yielding this output:

<element attr="content">
  <body>
    This line is indented by 4 spaces.
  </body>
</element>

There’s much more to raw string literals than this – for instance they support interpolation! Read more about raw string literals in the docs.

Abstracting over static members

How do you abstract over operations that are inherently static – such as operators? The traditional answer is “poorly”. In C# 11 we released support for static virtual members in interfaces, which was in preview in C# 10. With this you can now define a very simple mathematical interface:

public interface IMonoid<TSelf> where TSelf : IMonoid<TSelf>
{
    public static abstract TSelf operator +(TSelf a, TSelf b);
    public static abstract TSelf Zero { get; }
}

Notice how the interface takes a type parameter for “itself”. That’s because static members don’t have a this.

Anyone can now implement this interface by providing implementations for the two static members, and passing themselves as the TSelf type argument:

public struct MyInt : IMonoid<MyInt>
{
    int value;
    public MyInt(int i) => value = i;
    public static MyInt operator +(MyInt a, MyInt b) => new MyInt(a.value + b.value);
    public static MyInt Zero => new MyInt(0);
}

Importantly, how do you consume these abstract operations? How do you call virtual members when there is no instance to call them on? The answer is via generics. Here is what it looks like:

T AddAll<T>(params T[] elements) where T : IMonoid<T>
{
    T result = T.Zero;
    foreach (var element in elements)
    {
        result += element;
    }
    return result;
}

The type parameter T is constrained by the IMonoid<T> interface, and that allows the static virtual members of that interface – Zero and + – to be called on T itself!

Now we can call the generic method with some MyInts, and the correct implementations of + and Zero are passed in through the type argument:

MyInt sum = AddAll<MyInt>(new MyInt(3), new MyInt(4), new MyInt(5));

In fact .NET 7 comes with a new namespace System.Numerics chock-full of math interfaces, representing the different combinations of operators and other static members that you’d ever want to use: the “grown-up” versions of the little IMonoid<T> interface above. All the numeric types in .NET now implement these new interfaces – and you can add them for your own types too! So it’s now easy to write numeric algorithms once and for all – abstracted from the concrete types they work on – instead of having forests of overloads containing essentially the same code.

It’s also worth noting that static virtual members are useful for other things than math. For instance you can abstract over factory methods for a hierarchy of types. But we’ve covered enough for now – you might want to check out these tutorials in docs on static abstract interface methods and generic math.

Even if you do not create interfaces with static virtual members, you benefit from the improvements they make to .NET libraries, now and in the future.

List patterns

Pattern matching is one of the ongoing stories in C# that we just keep filling out. Pattern matching was introduced in C# 7 and since then it has grown to become one of the most important and powerful control structures in the language.

C# 11 adds list patterns to the story. With list patterns you can apply patterns recursively to the individual elements of list-like input – or to a range of them. Let’s jump right in with the generic algorithm from above, rewritten as a recursive method using list patterns:

T AddAll<T>(params T[] elements) where T : IMonoid<T> => 
    elements switch
{
    [] => T.Zero,
    [var first, ..var rest] => first + AddAll<T>(rest),
};

There’s a lot going on, but at the center is a switch expression with two cases. One case returns zero for an empty list [], where Zero is defined by the interface. The other case extracts the first element into first with the var first pattern, and the remainder is extracted into rest using the .. to slice out all the remaining elements into the var rest pattern.

Read more about list patterns in the docs.

Required members

Another ongoing theme that we’ve been working on for several releases is improving object creation and initialization. C# 11 continues these improvements with required members.

When creating types that used object initializers, you used to be unable to specify that some properties must be initialized. Now, you can say that a property or field is required. This means that it must be initialized by an object initializer when an object of the type is created:

public class Person
{
    public required string FirstName { get; init; }
    public string? MiddleName { get; init; }
    public required string LastName { get; init; }
}

It is now an error to create a Person without initializing both the required properties:

var person = new Person { FirstName = "Ada" }; // Error: no LastName!

Check the docs for more on required members.

In closing …

C# 11 also includes many other features. I hope this appetizer excites you to explore What’s new in C# 11 and that you have as much fun coding with C# 11 as we had making it! We strive to make the language ever more useful, not only by adding more expressive power (as with static virtual members) but also by simplifying, streamlining and removing boilerplate (as with e.g. raw string literals and list patterns) and making it safer (as with required members).

Category
.NETC#

Author

Mads Torgersen
C# Lead Designer

Mads is the lead designer for the C# programming language, and an architect on the .NET team at Microsoft.

21 comments

Discussion is closed. Login to edit/delete existing comments.

  • 龙 王

    Hope it can get better and better

  • Guy Cox

    and still no overridable assignment operator.

  • Davide Curioni

    Cool! How nice it would be if we could use the new versions of C# in old ASP.NET 4.8 Framework form applications too big to be upgraded to newer technologies!

  • Bruce Wayne · Edited

    Kudos for the release of C# 11. But as far as I can tell, there is still only 1 Internet on planet Earth. That's why I like to capitalize them both ('Internet' and 'Earth'). And will continue to do so until we change the rule of capitalization with regard to proper nouns.

    On 'The Internet'" Those are your words.

    Not on "an internet". If you had said something along the lines of "My internet isn't working" then...

    Read more
    • John Robinson

      Perhaps Bruce should spend less time ranting about misuse of “Internet” vs “internet” and more time learning about proper usage of “your” vs “you’re”?

      Anywho.. love the new features.

    • Dan Tinsley · Edited

      Hey, I think it's cool that you're trying to correct the author's writing issues, but I noticed that even you're not perfect, so you probably should proofread what you write first before ripping on someone else. It makes you look hypocritical.

      I'm mainly referring to the sentence, "But it looks your talking about “the one” and “the only” Internet on planet Earth in your post," in the third paragraph of your post. You misspelled the word...

      Read more
    • Aditya

      Well, now I know what Bruce Wayne does to pass his time.

    • Lior Cohen · Edited

      My word...what a giant douchecanoe we have here folks! All behold the pedantry and pageantry of the one and only cretin to find this post. While I certainly have better things to do than reply to a troll like yourself, I just HAD to. WTF are you going on about boy? First, the mention of the internet here was this line:

      "...whereas the prevailing string encoding on the internet is UTF-8."

      There is no doubt or question...

      Read more
      • Dan Tinsley

        To be fair, there actually used to be a difference. It once was that an internet was any generic inter-connection of smaller computer networks, whereas the Internet was the main internet that the world used, but now it’s acceptable to write “Internet” as “internet.” He technically wasn’t wrong. I guess he didn’t realize that times have changed and the lowercase version of “Internet” is acceptable now.

  • Felipe PessotoMicrosoft employee

    Just out of curiosity, abstract/virtual static members were supported by the runtime previously, or we also had to change it? Wondering how this works internally, similar to when we use structs + generics and the runtime generates separates assembly code for each one?

    Thanks

    • Mads TorgersenMicrosoft employee Author

      Static virtual members are a new feature in the runtime as well, so they won’t work on older runtimes.

  • javad mahdavian

    like that list patterns .. big like 👍👍

  • Hamed

    Abstract static makes some scenarios far simpler!

  • Mariusz

    Any chance to get blog post or better documentation about “scoped ref”? The docs are pretty dry and don’t have examples. I guess it’s pretty niche feature for performance related scenarios. Still, it would be nice to understand what this feature gives us.

    • Mads TorgersenMicrosoft employee Author

      You're right that the docs on scoped refs are pretty terse at the moment. After your comment we've opened this issue to track the need to flesh it out more. In the meantime, you can check out the feature spec, which is somewhat harder to read but also more complete! 😉

      Read more
  • David Lewis · Edited

    The list pattern example looks very much like something you could write in a ML language. Would this example be tail call optimized by the compiler in most scenarios?

    • Mads TorgersenMicrosoft employee Author

      We don't lean into tail call optimization in C#. It's somewhat rare that you could actually use it, and you have to be a bit of an expert to rely on it, so we haven't prioritized it as a feature. For the example with list patterns here, the recursive call is actually not a tail call - there's work to do (adding things) after it returns, so we have to keep the stack frame around.

      Read more
  • Amar Srayer

    My knowledge of C# just shrank.

    • Evandro Reis

      haha I can feel you