Exploring C# 11 Features

C# 11 is the latest version of C# language. C# 11 was released with the latest .Net 7. In this article, I would like to outline some of the new features in C# 11.

As a prerequisite, please ensure you have Visual Studio 17.1 (Visual Studio 2022 Update 1) with .NET 7 Preview 1 installed. Also, ensure the LangVersion is set to preview in your project settings. Following are my configuration.

<TargetFrameworks>net7.0;</TargetFrameworks>
<LangVersion>preview</LangVersion>

Generic Attribute in C# 11

Honestly, I have missed this a lot previously. It is not a common requirement, but there were scenarios when you would wish that the language would not stop you from creating a Generic Custom Attribute. With C# 11, the .Net team has provided a way for developers to write their custom generic attribute.

For example,

public class FooAttribute<T>:Attribute
{
    public T FooProperty {get;set;}
    public FooAttribute<T>(T fooValue) => FooProperty = fooValue;
}
public class Bar
{
    [Foo<String>]
    public string Name{get;set;}
}

NewLine in Non-verbatim Interpolation in C# 11

Consider the following code.

var item = new
{
    Name = "fooBar",
    Price = 50
};
var _ = $"Item Price: {item.Name
    .ToUpper()}";

Previously, in C# 10, the code would not compile. You were not allowed to use newline in non-verbatim ($"")string interpolations. This restriction has been removed now allowing you to define newline in non-verbatim string interpolation.

NotNull checks in C# 11

Update : The NotNull checks has been now left out of the C# 11 Preview 7 and is not likely to make it for the release.

Previously in .Net 10, the .Net Team introduced a new and cleaner way of handling nulls checks in preconditions.

public void Foo(string name)
{
    ArgumentNullException.ThrowIfNull(nameof(name));
    // Rest of code
}

In C# 11, the team has gone a step further to make it less verbose by introducing the "!!" operator. The above code could now be rewritten as

public void Foo(string name!!)
{
    // Rest of Code
}

That is less verbose, but I am still having second thoughts on how that would improve the readability of the code. Maybe a little bit of verbose is still good :)

List and Slice Pattern in C# 11

Pattern match has been evolving over the past couple of versions of C# and it continues to do so in C# 11 as well. List Pattern and Slice Pattern are the new entrants in the pattern collections which are getting richer with each C# version.

List Pattern, as mentioned in the proposal, is used to match the elements. For example,

var l1 = new[] { 1, 2, 3, 4, 5 };

The above list would match any of the following List Pattern

[1, 2, 3, 4, 5]  // Matches specific elements and length of collection
[_,_,_,_,5]  // Any collection that matches length 5 and has 5 as last element.

For example

if(l1 is [1, 2, 3, 4, 5] && l1 is [_,_,_,_,5])
{
    Console.WriteLine("List Pattern Matched");
}

The Slice Pattern amplifies the power of List Pattern further. For example, if you need to match any collection that has less than or equal to 5 elements with the last element as 5, you can modify the above pattern as

if (l2 is [.., 5])
{
    Console.WriteLine("List Pattern Matched 3");
}

The [..,5] is equivalent to list.Length>=1 && list[^1]==5.

Let us look into some more examples with List and Slice Patterns.

var l1 = new[] { 1, 2, 3, 4,5 };
var l2 = new[] { 1, 3, 5 };
var l3 = new[] { 1};
var l4 = new[] { 9 };
var l5 = new[] { 1,6 };

string PatternTest(int[] collection) => collection switch
{
    [1, 2, 3, 4, 5] => "first", // list
    [1, 3, .., 5] => "second", // slice in list
    [1, _] => "third", // list
    [1, ..] => "fourth", //slice in list
    [..] => "fifth" // slice
};

The above patterns could be read as

[1, 2, 3, 4, 5]
/*
if(collection.Length == 5
        && collection[0] == 1
        && collection[1] == 2
        && collection[2] == 3
        && collection[3] == 4
        && collection[4] == 5)
*/

[1, 3, .., 5]
/*
collection.Length  >= 3
&& collection[0] == 1
&& collection[1] == 3
&& expr[^1] == 5
*/

[1, _]
/*
collection.Length == 2
&& collection[1] == 1
*/

[1,..]
/*
collection.Length >= 1
&& collection[1] == 1
*/

[..]
/*
Matches any collection with zero or more elements
*/
Console.WriteLine(PatternTest(l1));
Console.WriteLine(PatternTest(l2));
Console.WriteLine(PatternTest(l3));
Console.WriteLine(PatternTest(l4));
Console.WriteLine(PatternTest(l5));

// output
// first
// second
// fourth
// fifth
// third

You could also capture the result of slice. For example,

var l1 = new[] { 1, 2, 3, 4,5 };
if(l1 is [ 1,2, .. var vals, 5 ])
{
    Console.WriteLine(String.Join(",", vals));
}
// Output
// 3,4

Conclusion

So far we have seen some new features coming in C# 11. From what was seen so far, the Generic Attribute and List/Slice patterns are my favorites, but these promise to be just the tip of the iceberg. We will continue our journey and explore new features as they are released. For more news updates on proposals and their status, check our the Proposal page.

Here is another key article talks about C# 11 features: New Features In C# 11 | Learn C# (c-sharpcorner.com) 


Similar Articles