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.

  • Oscar Järgren 0

    What a dissapointment C#11 seems to be. Why is C# moving towards “Easy to Write, but hard to read”?

    What’s been great about C# has always been the ease of understanding the code, that it looks good.

  • Andrew Witte 0

    That “Foo(string value!!)” is totally pointless. If anything just make it an optional [NotNull] attribute & be done with it.

    Why not focus on actual missing features that were missed when unsafe was being updated?
    This should exist: fixed multidimensional-arrays (aka fixed float values[][])
    This should exist: fixed custom value-type arrays (aka fixed MyStruct values[])

  • Константин Стуков 0

    !! this is a good feature that will allow us to get closer to the ideal C#, where the NRT feature would work not only in design time. I think we would get to this and so, only belatedly, to .Net 8/.Net 9. Hence my suggestion:
    Besides !!, also add the #nullable strict mode in addition to the #nullable enable mode (as well as the Strict MSBuild property) which would automatically apply !! to all public/internal/protected methods that have NRT enabled. This would allow for a smoother transition to the ideal NRT. The presence of the #nullable directive would allow to disable this feature for specific files and apply the dot !!. However, for most small advanced libraries it would be enough to just include Nullable Strict MSBuild Property and it would work like https://github.com/Fody/NullGuard in NRT mode.

  • Martin Sedlmair 0

    I partly agree with some others. I also have a lot of code that has the

    if (is null) throw new ArgumentNullException();

    and I would gladly see this code vanishing. I also like the NRTs although for an already existing large code base its hard to get it in.

    There are many more things though I can actually not do with the language or what is causing a lot of work, is too verbose is creating obscure code to perform the things I want to do. Instead a lot of effort is put into syntactic sugar that of course makes code more readable, realiable and safe – and don’t get me wrong – I like many of these additions and they have tremendously improved my code – BUT – these things could have all been done before.

    The new interpolated strings are something similar. I can already do this now, maybe with a bit more effort but it’s not impossible. But maybe for ASP.NET and Blazor this is important!?

    A lot of things have not been well thought but obviously forced to be in the language like empty struct constructor and struct field initializers, very cool and I very much needed them. But obviously something was not fully discussed and after the introduction some of the new features were changed. I don’t want to discuss the details and the changes might be there for a reason but what is interesting that there are long running discussions about the pros and cons so one could think that such things are thoroughly discussed and tested (I had many of these discussions). So it seems like green bananas are put into the language.

    Additionally, to name another example, Span very cool, very much needed was introduced with some tricks that it works because it cannot be expressed with C# (ByRef). This is what I mean I couldn’t have created Span myself because C# is missing features that are of high relevance, instead the ByRef trick was introduced, instead of thinking about introducing ref field which would maybe have solved the whole ref struct trickery.

    While all of us have their own problems and requests, there are a many common problems Shapes, Extensions, Roles, Math on Generics, Self types, deep immutability, Reference types on stack, just to name a few where real effort is needed and they are slowly getting more effort, are treated stepmotherly (at least it seems). I don’t know why this is the case, maybe for some reason some things are harder to solve (of course) and need more discussions, but most of the other new features are “simple” code rewrites – not all of them for sure.

    From a lot of discussions I can hear from some language team members: “use an analyzer to solve your problems”. While this true for some requests this is not true for the majority but still there are a few language team members that insist on using them. Maybe they haven’t understood how a development team works. Its not that if you raise your fingers and saying with an analyzer “don’t do it” that it is obeyed by the developer. A language should prevent mistakes and not inform you about mistakes.

    In general it would be nice where C# is heading too and also some explanations why things have been prioritized. Are you focussing on satisfying Python developers and attract them? It seems that many suggestions are mostly ignored (sometimes understandable if people are asking if we can get rid of the semicolon ;-)) Things seem to be prioritized as there is need from Microsoft itself (which I can understand from a company standpoint) but then really the questions is why there are discussions at all. I can also understand that the language team is maybe not as large as everyone thinks and that there are not 100 people working on the language but maybe only 10 or 20 (I’m feeling with you).

    As a last word, there are many people loving C# and .NET and the improvements over the last years but I can als see people drifting away because they have to wait too long for required features and cannot do what they want to achieve.

  • Onyx Gc 0

    Ive been using c# for 15 years
    But now I have to say,

    I dont like the new features, it makes the language ugly and dirty. It doesnt add much value.

  • Andrew Bowen 0

    Love the new null reference check.

    Care factor for !! or something else is low. Keep up the good work!

  • David Taylor 0

    Kathleen, I would love to see the primary constructor feature finished in C# 11. I have a few times implemented services as records, because records effectively have primary constructors already, but immediately after doing this feel dirty. .NET so heavily uses dependency injection that it is common to have a service class that has 6-7 fields, and a constructor taking these 6-7 objects that initializes the fields. All that code goes away with primary constructors – often 10-15 lines of code just goes away.

    It is painful that C# (since v9) has supported the feature with records, but normal classes do not yet support primary constructors. My vote for sure.

    • Kathleen DollardMicrosoft employee 0

      You can keep up with language features on the C# Feature Status page.

      Not everything on the list will make it in (and we may change or pull features in preview), but you’ll be glad to know that primary constructors is on the list. You’ll find links to the issue and proposal and LDM discussions, and you can ping the issue to show your support, or if you think you have a reason not yet mentioned (I’m not sure DI has been mentioned. Or you can voice your support in the CSharpLang discussions

  • Markus Hjärne 0

    I would prefer a syntax like

    Foo(checked string value) 

    if that’s possible…

  • Michael Taylor 0

    Have to agree with the others that C# has switched from a “better C++” to an overly complex “functional-like” programming language with lots of syntactical rules to solve boundary cases. The language was actually fine until it open sourced and everybody started throwing their own preferences into the mix. Now we have complex pattern matching switch expressions (not statements) that may reduce the code you write but doesn’t reduce the cognitive load to understand. I’m so tired of feature XYZ being added to the language because somebody somewhere wants to commit a design and PR to solve their very specific problem. Don’t even get me started on the differences between struct, class, readonly struct, ref struct, ref class and records…

    NRT is still a half-baked feature to me (since it is literally only for C# code basically and so provides no actual protection) along with most of the other new features added like implicit using, top level statements, etc. While I can see these being useful in some situations (like demo code, samples and perhaps for somebody who has no idea how to code) the reality is that they quickly break down with more realistic code so anybody who learned on the “new” features still have to know the more formal stuff. Honestly I’m not wasting my productivity on imports (which can be auto-generated by IDE) or top level statements (which templates already generate code for). I spend my time refactoring code that no amount of language features are going to solve because they are specific to my problem area.

    In regards to !! I agree the syntax is unclear to anyone other than a language person. Furthermore it doesn’t really help because, in the case of strings at least, you still have to check for empty string as well (because most code considers null and empty to be the same thing). Personally I think adding a new compile time attribute (like Conditional) is the better route. It already exists in the language, it is clear what it is doing, you can apply it per parameter and the compiler can auto-generate code from it. Basically like Conditional already does. Instead we have yet another operator to try and understand. Seriously, let the language designer wannabes build their own language. Maybe call it APL.NET…

    • Oleg Mikhailov 0

      I agree, the previous approach, when the development of the language was determined by voting among professionals, worked better. At least, those people were interviewed when they were hired at Microsoft. As for members of the open source community, unfortunately, it is often impossible even to be sure about their mental health.

  • dimohy slogs.dev 0

    Well, I don’t know why people aren’t happy with this handy feature of adding !! to a method argument to raise an exception if the argument is null.

    Think of a new C# developer. Adding !! to a method argument is easier than anything else. You just need to be aware that the method can misbehave when null and deploy.

    However, as C# grows, it tends to add more and more complex syntax. I think we need to look at this from a different perspective.

    That said, I think developers new to C# need a book or a guide document, or lots of sample code, that will allow them to take advantage of the latest additions without having to learn everything from the past.

Feedback usabilla icon