Early peek at C# 11 features

Kathleen Dollard

Visual Studio 17.1 (Visual Studio 2022 Update 1) and .NET SDK 6.0.200 include preview features for C# 11! You can update Visual Studio or download the latest .NET SDK to get these features.

Check out the post Visual Studio 2022 17.1 is now available! to find out what’s new in Visual Studio and the post Announcing .NET 7 Preview 1 to learn about more .NET 7 preview features.

Designing C# 11

We love designing and developing in the open! You can find proposals for future C# features and notes from language design meetings in the CSharpLang repo. The main page explains our design process and you can listen to Mads Torgersen on the .NET Community Runtime and Languages Standup where he talks about the design process.

Once work for a feature is planned, work and tracking shifts to the Roslyn repo. You can find the status of upcoming features on the Feature Status page. You can see what we are working on and what’s merged into each preview. You can also look back at previous versions to check out features you may have overlooked.

For this post I’ve distilled these sometimes complex and technical discussions to what each feature means in your code.

We hope you will try out these new preview features and let us know what you think. To try out the C# 11 preview features, create a C# project and set the LangVersion to Preview. Your .csproj file might look like:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <LangVersion>preview</LangVersion>
    </PropertyGroup>
</Project>

C# 11 Preview: Allow newlines in the “holes” of interpolated strings

Read more about this change in the proposal Remove restriction that interpolations within a non-verbatim interpolated string cannot contain new-lines. #4935

C# supports two styles of interpolated strings: verbatim and non-verbatim interpolated strings ($@"" and $"" respectively). A key difference between these is that a non-verbatim interpolated strings cannot contain newlines in its text segments, and must instead use escapes (like \r\n). A verbatim interpolated string can contain newlines in its text segments, and doesn’t escape newlines or other character (except for “” to escape a quote itself). All of this behavior remains the same.

Previously, these restrictions extended to the holes of non-verbatim interpolated strings. Holes is a shorthand way of saying interpolation expressions and are the portions inside the curly braces that supply runtime values. The holes themselves are not text, and shouldn’t be held to the escaping/newline rules of the interpolated string text segments.

For example, the following would have resulted in a compiler error in C# 10 and is legal in this C# 11 preview:

var v = $"Count ist: { this.Is.Really.Something()
                            .That.I.Should(
                                be + able)[
                                    to.Wrap()] }.";

C# 11 Preview: List patterns

Read more about this change in the proposal List patterns.

The new list pattern allows you to match against lists and arrays. You can match elements and optionally include a slice pattern that matches zero or more elements. Using slice patterns you can discard or capture zero or more elements.

The syntax for list patterns are values surrounded by square brackets and for the slice pattern it is two dots. The slice pattern can be followed by another list pattern, such as the var pattern to capture the contents of the slice.

The pattern [1, 2, .., 10] matches all of the following:

int[] arr1 = { 1, 2, 10 };
int[] arr1 = { 1, 2, 5, 10 };
int[] arr1 = { 1, 2, 5, 6, 7, 8, 9, 10 };

To explore list patterns consider:

public static int CheckSwitch(int[] values)
    => values switch
    {
        [1, 2, .., 10] => 1,
        [1, 2] => 2,
        [1, _] => 3,
        [1, ..] => 4,
        [..] => 50
    };

When it is passed the following arrays, the results are as indicated:

WriteLine(CheckSwitch(new[] { 1, 2, 10 }));          // prints 1
WriteLine(CheckSwitch(new[] { 1, 2, 7, 3, 3, 10 })); // prints 1
WriteLine(CheckSwitch(new[] { 1, 2 }));              // prints 2
WriteLine(CheckSwitch(new[] { 1, 3 }));              // prints 3
WriteLine(CheckSwitch(new[] { 1, 3, 5 }));           // prints 4
WriteLine(CheckSwitch(new[] { 2, 5, 6, 7 }));        // prints 50

You can also capture the results of a slice pattern:

public static string CaptureSlice(int[] values)
    => values switch
    {
        [1, .. var middle, _] => $"Middle {String.Join(", ", middle)}",
        [.. var all] => $"All {String.Join(", ", all)}"
    };

List patterns work with any type that is countable and indexable — which means it has an accessible Length or Count property and with an indexer an int or System.Index parameter. Slice patterns work with any type that is countable and sliceable — which means it has an accessible indexer that takes a Range as an argument or has an accessible Slice method with two int parameters.

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.

C# 11 Preview: Parameter null-checking

Read more about this change in the proposal Parameter null checking.

We are putting this feature into this early preview to ensure we have time to get feedback. There have been discussions on a very succinct syntax vs. a more verbose one. We want to get customer feedback and from users that have had a chance to experiment with this feature.

It is quite common to validate whether method arguments are null with variations of boilerplate code like:

public static void M(string s)
{
    if (s is null)
    {
        throw new ArgumentNullException(nameof(s));
    }
    // Body of the method
}

With Parameter null checking, you can abbreviate your intent by adding !! to the parameter name:

public static void M(string s!!)
{
    // Body of the method
}

Code will be generated to perform the null check. The generated null check will execute before any of the code within the method. For constructors, the null check occurs before field initialization, calls to base constructors, and calls to this constructors.

This features is independent of Nullable Reference Types (NRT), although they work well together. NRT helps you know at design time whether a null is possible. Parameter null-checking makes it easier to check at runtime whether nulls have been passed to your code. This is particularly important when your code is interacting with external code that might not have NRT enabled.

The check is equivalent if (param is null) throw new ArgumentNullException(...). When multiple parameters contain the !! operator then the checks will occur in the same order as the parameters are declared.

There are a few guidelines limiting where !! can be used:

  • Null-checks can only be applied to parameters when there is an implementation. For example, an abstract method parameter cannot use !!. Other cases where it cannot be used include:
    • extern method parameters.
    • Delegate parameters.
    • Interface method parameters when the method is not a Default Interface Method (DIM).
  • Null checking can only be applied to parameters that can be checked.

An example of scenarios that are excluded based on the second rule are discards and out parameters. Null-checking can be done on ref and in parameters.

Null-checking is allowed on indexer parameters, and the check is added to the get and set accessor. For example:

public string this[string key!!] { get { ... } set { ... } }

Null-checks can be used on lambda parameters, whether or not they are surrounded by parentheses:

// An identity lambda which throws on a null input
Func<string, string> s = x!! => x;

async methods can have null-checked parameters. The null check occurs when the method is invoked.

The syntax is also valid on parameters to iterator methods. The null-check will occur when the iterator method is invoked, not when the underlying enumerator is walked. This is true for traditional or async iterators:

class Iterators {
    IEnumerable<char> GetCharacters(string s!!) {
        foreach (var c in s) {
            yield return c;
        }
    }

    void Use() {
        // The invocation of GetCharacters will throw
        IEnumerable<char> e = GetCharacters(null);
    }
}

Interaction with Nullable Reference Types

Any parameter which has a !! operator applied to its name will start with the nullable state being not-null. This is true even if the type of the parameter itself is potentially null. That can occur with an explicitly nullable type, such as say string?, or with an unconstrained type parameter.

When !! syntax on parameters is combined with an explicitly nullable type on the parameter, the compiler will issue a warning:

void WarnCase<T>(
    string? name!!,     // CS8995   Nullable type 'string?' is null-checked and will throw if null. 
    T value1!!        // Okay
)

Constructors

There is a small, but observable change when you change from explicit null-checks in your code to null-checks using the null validation syntax (!!). Your explicit validation occurs after field initializers, base class constructors, and constructors called using this. Null-checks performed with the parameter null-check syntax will occur before any of these execute. Early testers found this order to be helpful and we think it will be very rare that this difference will adversely affect code. But check that it will not impact your program before shifting from explicit null-checks to the new syntax.

Notes on design

You can hear Jared Parsons in the .NET Languages and Runtime Community Standup on Feb. 9th, 2022. This clip starts about 45 minutes into the stream when Jared joins us to talk more about the decisions made to get this feature into preview, and responds to some of the common feedback.

Some folks learned about this feature when they saw PRs using this feature in the .NET Runtime. Other teams at Microsoft provide important dogfooding feedback on C#. It was exciting to learn that the .NET Runtime removed nearly 20,000 lines of code using this new null-check syntax.

The syntax is !! on the parameter name. It is on the name, not the type, because this is a feature of how that specific parameter will be treated in your code. We decided against attributes because of how it would impact code readability and because attributes very rarely impact how your program executes in the way this feature does.

We considered and rejected making a global setting that there would be null-checks on all nullable parameters. Parameter null checking forces a design choice about how null will be handled. There are many methods where a null argument is a valid value. Doing this everywhere a type is not null would be excessive and have a performance impact. It would be extremely difficult to limit only to methods that were vulnerable to nulls (such as public interfaces). We also know from the .NET Runtime work that there are many places the check is not appropriate, so a per parameter opt-out mechanism would be needed. We do not currently think that a global approach to runtime null checks is likely to be appropriate, and if we ever consider a global approach, it would be a different feature.

Summary

Visual Studio 17.1 and .NET SDK 6.0.200 offer an early peek into C# 11. You can play with parameter null-checking, list patterns, and new lines within curly braces (the holes) of interpolated strings.

We hope you’ll check out the C# 11 Preview features by updating Visual Studio or downloading the latest .NET SDK, and then setting the LangVersion to preview.

We look forward to hearing what you think, here or via discussions in the CSharpLang repo on GitHub!

99 comments

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

  • Олег Нечитайло 0

    I don’t really like the !! operator.

    I successfully use Fody.NullGuard to inject nullchecks everywhere for runtime and nullable reference type analyzers are good at compile time.

    They work perfectly together.

    This adds additional syntax sugar for a specific edge case that should be solved by a type system.

    I expect to just write Foo(string bar) and that should be enough information for the compiler to see that it should not be null.

    • cs 0

      These “null” checks, are effectively “code weaving/codegen”….albeit at compile.

      Thus, instead of introducing random syntax onto things, a longer term common way to “introduce” code weaving/codegen to apply to “things” could have been attempted.

      That didn’t have to be via attributes/metadata that was done at runtime…..it could have been some kind of compile time decorate “enhanced attribute”.

      I would see the IDE/editor when you are using the “parameter” null checking “enhanced attribute” help you to “apply” it to “parameters” with intellisense, highlighting etc

      Fody has a clear way of doing that, and there are other ways.

      This use of obscure operators, which are harder to search/find (because they get mixed up with the operator used in other contexts), etc, isn’t the way to go

      It’s also changing/breaking the code flow…..for instance, when debugging now…..and I want to set a breakpoint ON the parameter null check “before” it is raised, can I do that ?….things like that.

      This “operator” way, and “assume” the null checks are done right at the start of the method body is inflexible.

      What if I want to do something before the null checks are done….for instead “log” the fact I have entered a method, or so some other “checks” before the null ones…..I don’t have that flexibility.

      To me !! means not not…..i.e. do nothing.

      If you gave someone those 2 characters, and ask them to find out what it means……they would be struggling to infer it.

  • Ian Marteens 0

    Not impressed at all by the evolution of C# since version 7, to be honest.

  • Will Woo 0

    “holes” (cringe) is a dreadful choice for a non-technical alias for “interpolation expressions,” here: matching curly braces are placeholders for content evaluated at run-time.

    The term “interpolation expressions” refers to the use of String.Format that inserts content defined outside the body of the string into numbered curly braces inside the body of the string.

    Making a “new feature” out of something you can already do easily ?

            // nested late-binding, use of /r and /n not necessary
            int i01 = 100;
    
            string s01 = $@"Count ist: {$@"this.Is.Really.Something()
                .That.I.Should({i01}
                    be + able)[
                    to.Wrap()]"}";
    
  • Tyler 0

    Don’t like this at all either. Developers can throw NullReferenceExceptions if they want to, plus it’s easy to add in Visual Studio. This all feels like more syntax noise that no one asked for.

    • Stuart Ballard 0

      I don’t quite get the logic behind the idea that “void Foo(string arg!!) { … }” is somehow more syntax noise than “void Foo(string arg) { if (arg is null) throw new ArgumentNullException(nameof(arg)); … }

      Besides, I’d expect the compiler to complain about the latter being unreachable code if NRT is enabled so I’m not sure what the current best practice actually is for throwing ArgumentNullExceptions. Even if your calling code is compiled with NRT it’s still easy to send a null into a method that doesn’t want them – as easy as adding a single ! after the expression. And it’s not necessarily true that you’ll get an NRE instead – you could get just plain wrong behavior. Or you could get an NRE at an inconvenient point that leaves your object in an invalid state.

      I do kind of agree that it was a weird decision to make NRTs compile-time only (in particular I really wish that x! was syntax for inserting a runtime null check as opposed to a runtime no-op – it feels like it should be roughly equivalent to an explicit cast from T? to T) but that ship sailed years ago and they can’t retroactively change it now without breaking a ton of code.

  • Hassan Khademi 0

    I’m afraid, I think C# has decided to move away from being a simple readable understandable language that people love, more towards a cryptic syntax, which is hard to understand for people who have been coding with it for years, let alone new folks who want to start learning this language. It seems the only focus is currently on making the syntax as concise and brief as possible by introducing new symbols and double-symbols. I think C# should take a step back and rethink about the points I made. Otherwise in future, am I expected to figure out why this piece of code is wrong, at a glance? var x=[..]?!!=>$”{()=>~0>>[1,_ ]}”;
    Having said that, the other proposed feature “Allow newlines in the holes of interpolated strings” is nice and very useful.

  • Antya Dev 0

    Thanks for !! Null check. It really saves tons of extra code lines for our team and me.

  • Vartika Gupta 1

    IMO the null check !! operator will be able to save a few lines of code when one actually has to throw an ANE. However when I look from the perspective of a new dev, I feel it might become a little overwhelming to keep decoding the meanings of various special characters in C#. If there is an option of giving a clearer/more obvious way to express this, it would be better.

    • Luís Rodrigues 0

      Agreed

  • Luís Rodrigues 0

    So, assuming Nullabe is enabled:

    public void SomeMethod(string? param)

    Means param could be null, and that’s ok/allowed

    public void SomeMethod(string param)

    Means null shouldn’t be allowed in param but if param is null the developer must account for it and the IDE may add a warning about it

    public void SomeMethod(string param!!)

    Means if param is null an ArgumentNullException Will be thrown on method call regardless of what code is in the method

    If This is correct !! Is in a weird place where it’s usefullness is not very high.

    Without it, if param is null and the developer doesn’t check it the result would be about the same – null exception

    I think the ONLY use case for it is the developer doesn’t check but want’s to make sure if it’s null nothing is done. After all, without it the exception would ONLY be thrown when param is used, and anything the method does prior would already been done

  • Bassem Mohsen 1

    Thanks for the post Kathleen!

    The !! feature is interesting. But I can think of two issues:

    1) For checking string arguments, I use

    string.IsNullOrWhiteSpace(str)

    much more often that I use

    str is null

    Now I am not sure if I should use

    string str!!

    to make the code more clear or if this will be redundant.

    2) I personally prefer having all my arguments validation logic in one section of code at the beginning of the method body. I prefer not to mix my validation logic with other things and spread it in multiple places. This is why I do not use the throw expression to validate arguments

    _str = str ?? throw
    • Kathleen DollardMicrosoft employee 0

      !! would not replace the IsNullOrWhitespace method. it could replace variations of:

      _str = str ?? throw
      
  • Joel Dumas 0

    When is NRT gonna be completed?
    C# releases fly by and the initialization problem is not addressed yet.

    NRT are great, but the fact that their initialization is not tracked outside ctor leads to lots of type-safety holes and ugly “= null!” dummy initializers.
    This issue has been open on github since the release of NRT with lots of participants…

    NRT is one of the best thing that happened to .net in recent years, it deserves that the team focuses on finishing and polishing it rather than exploring new avenues 🙁

Feedback usabilla icon