What’s new in C# 11

C# 11 is coming this autumn and we can’t wait to show it to you. Who knows what astonishing features the .NET team has delivered to us this year? Without hesitation let’s dive right into it!

Record types: Record types are value types that are designed to be used as simple data containers. They provide automatic implementations of Equals, GetHashCode, and ToString, and support deconstruction for easy data access.
Init-only properties: Init-only properties allow you to set the value of a property only during object initialization, making it read-only thereafter.
Top-level programs: Top-level programs allow you to write standalone programs that don’t require a class or method wrapper.
Target-typed new expressions: Target-typed new expressions allow you to omit the type when creating a new object, and the type is inferred from the context.
Improved pattern matching: C# 11 introduced several new pattern matching features, such as the is pattern, the switch expression, and the when clause.
Range and index types: The range and index types provide a concise syntax for working with ranges and indexes of arrays and strings.
Null-coalescing assignment: The null-coalescing assignment operator (??=) allows you to assign a value to a variable only if it is null.
Async streams: Async streams allow you to use the await keyword to asynchronously iterate over a collection of items.
Unmanaged generic constraints: Unmanaged generic constraints allow you to specify that a type parameter must be an unmanaged type.
Static anonymous functions: Static anonymous functions allow you to create anonymous functions that are bound to the enclosing type, rather than to an instance of the type.
Stackalloc in nested expressions: The stackalloc keyword can now be used in nested expressions, allowing you to allocate memory on the stack from within a lambda or other nested expression.

File scoped types 📂

There are lots of visibility modifiers in C# already and they’ve decided to put effort into makings of another one! Yeah, it may be overwhelming, but besides classic private, protected, public, and a little bit confusing protected internal, there is also a modifier regarding assembly visibility: internal and protected internal.

Even though later ones may be unfamiliar to an average programmer, the C# programmers know it perfectly well: it’s assembly-related visibility modifiers! And assembly is a central notion in C#. But as much as one loves assemblies, one also wants to be more specific with a more concrete type of notion: files.

And that’s the reason why you’d want to use a new file access modifier. It is used to limit the visibility of a type to a single file. As .NET team notes: “this feature helps source generator authors avoid naming

What’s new in C# 11

collisions”. But who knows what the use case of that feature may be in the future?

Raw string literals ✏️

A casual Python programmer already knows where this is going. Just take a look at an example:

string sonnet129 = """
	Is lust in action: and till action, lust
	Is perjured, murderous, bloody, full of "blame",
	Savage, extreme, rude, cruel, not to trust;
	Enjoyed no sooner but despised straight;

Yeah, you can use multiline strings with any quotes inside, just like that! If you want to put a variable into the string use the $$ syntax with raw strings:

var name = "World";
var greeting = $$"""
	Hello, {{{name}}!

Generic attributes 🧠

This is one of the features that library developers will value the most. You know how most of the time your users would use [YourAttribute(typeof(TheType))] to let you know the type? And then there’s lots of reflection/metaprogramming got involved.

Well, now you can simply create generic attributes, just like that:

using System;

public class YourAttribute<T> : Attribute {}

public class TheType {}

Of course, there are limitations:

  • Type T can’t be substituted for dynamic, nullables, tuples
  • Type T can’t be used as a generic runtime parameter, see:
public class MyGenericType<T>
   [YourAttribute<T>()] // Not allowed!
   public string MyMethod() => String.Empty;

List patterns 🔋

Pattern matching is yet another powerful tool coming from more functional programming languages that we still only figuring out how to use properly. Nevertheless, C#, which has always been on the cutting edge of programming language technology, has something to surprise us today.

In previous versions, an interesting technique was revealed called “pattern matching”. In short, it helps one to do something like this:

using System;

var order = new MyOrder(7, 750);
Console.WriteLine(GetDiscount(order)); // Outputs 0.050
decimal GetDiscount(MyOrder o) =>
    o switch
        { Number: > 11, Price: > 1001.00m } => 0.1m,
        { Number: > 6, Price: > 501.00m } => 0.050m,
        { Price: > 251.00m } => 0.020m,
        null => throw new ArgumentNullException(nameof(o), "Can't get a discount"),
        _ => 0.0m,
record MyOrder(int Number, decimal Price);

But right now the pattern matching is added to C# List<T>:

using System;
using System.Collections.Generic;

var list = new List&amp;amp;amp;amp;amp;lt;int&amp;amp;amp;amp;amp;gt; { 1, 2, 3 };
Console.WriteLine(Cases(list)); // prints 1

int Cases(List&amp;amp;amp;amp;amp;lt;int&amp;amp;amp;amp;amp;gt; list)
    =&amp;amp;amp;amp;amp;gt; list switch 
            [1, _, 3] =&amp;amp;amp;amp;amp;gt; 1, // [1, 2, 3] or [1, 1, 3] ...
            [0, ..]   =&amp;amp;amp;amp;amp;gt; 2, // [0, 1, 2] or [0, 5] or [0] ...
            [1, ..]   =&amp;amp;amp;amp;amp;gt; 3, // [1, -1, 2] or [1, 5, 3] ...
            []        =&amp;amp;amp;amp;amp;gt; 4, // only empty
            // handle all of the other cases:
            _  =&amp;amp;amp;amp;amp;gt; throw new ArgumentOutOfRangeException($"{list}")

So once again:

  • .. matches any 0 or more elements
  • _ matches any single element
  • [] matches the empty list
  • Any list of values matches that values 🙂

Required members 🙌


— What can be better than a new keyword in a C#? — A useful keyword!

And this time it’s a hell of a useful keyword! Remember all the time when you were wondering how to specify that the member of a type(class, struct, record) should be definitely initialized? This time the .NET team decided to take care of it and declared a new keyword: required.

You can use it like that:

using System;

var address = new Address { FirstLine = "Wall st. 57", PostCode = 22101 };

class Address
    public required string FirstLine { get; init; }
    public string? SecondLine { get; init; }
    public string? ThirdLine { get; init; }
    public required decimal PostCode { get; init; }

    public override string ToString() =&amp;amp;amp;amp;amp;gt; $"{FirstLine}:{SecondLine}:{ThirdLine}:{PostCode}";

So it’s basically an error if you try to create Address without FirstLine and PostCode fields. You need to either initialize it in the constructor or in an object initializer syntax(like the one in the example above).

Generic math support 🦄

I know, I know… It’s been a lot of time since the first generic math request was posted! I can’t believe we made it this far. I mean to employ new features it’s ok, but to propagate a feature this big into a well-established standardized and fully-grown mature language like C# – this should be really hard. And I just want to thank the developers in the .NET team and from all over the world for working on this. We finally have it: generic math!

Before it became possible to incorporate this, some changes to the language should’ve been merged. The core feature on this way is static abstract:

Static abstract

You can add static abstract members to interfaces. These ones are analog to ordinary interface members, except that these ones are static, so they should be implemented as static members on a subclassing type. For example, you will definitely need to implement System.IAdditionOperators<TSelf, TOther, TResult>

if you want your type to support + operator. See how it’s being implemented in an ordinary integer class:

public readonly struct Int32
	// ...
	static int IAdditionOperators&amp;amp;amp;amp;amp;lt;int, int, int&amp;amp;amp;amp;amp;gt;.operator +(int left, int right) 
		=&amp;amp;amp;amp;amp;gt; left + right;
	// ...

This way all operators in C# may be put in an interface to specify that these should be overloaded by its implementers. Also, there’s static virtual working in the same way as static abstract except that this one should be implemented also on an interface(more or less like a default interface method implementations).

Generic math usage

You can use it with already existing numeric types. For example see how we do this via INumber<T> interface:

using System;
using System.Numerics;

Console.WriteLine(ComputeAbstractly(1, 2));
Console.WriteLine(ComputeAbstractly(1.5, 2.9));

TNum ComputeAbstractly&amp;amp;amp;amp;amp;lt;TNum&amp;amp;amp;amp;amp;gt;(TNum a, TNum b) where TNum : INumber&amp;amp;amp;amp;amp;lt;TNum&amp;amp;amp;amp;amp;gt;
    return a + b * a;

Also, you can just create your own numeric type by implementing INumber<T>. But that interface has lots of members to override, so let’s just focus on a simpler version of implementing IMonoid<T>:

using System;
using System.Numerics;

var a = new CustomInteger(1);
var b = new CustomInteger(2);

Console.WriteLine(a + b);              // prints CustomInteger{3}
Console.WriteLine(CustomInteger.Zero); // prints CustomInteger{0}

class CustomInteger : IMonoid&amp;amp;amp;amp;amp;lt;CustomInteger&amp;amp;amp;amp;amp;gt;
    int _inner;
    public CustomInteger(int num) 
        =&amp;amp;amp;amp;amp;gt; _inner = num;
    public override string ToString() 
        =&amp;amp;amp;amp;amp;gt; "CustomInteger{" + _inner.ToString() + "}";
    public static CustomInteger operator +(CustomInteger a, CustomInteger b) 
        =&amp;amp;amp;amp;amp;gt; new CustomInteger(a._inner + b._inner);
    public static CustomInteger Zero 
        =&amp;amp;amp;amp;amp;gt; new CustomInteger(0);

More on generic math in a more detailed fashion is here.

Ending 🌄

So as you see there’s a lot of new stuff! Yeah, I really guess Christmas came earlier this year, huh? I am once again thankful to the community and developers for this set of new features. I suspect that there may be more coming in any time soon!


Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.