12个 C# 11 特性

所需成员

C# 11required为属性和字段引入了一个新的修饰符,以强制构造函数和调用者初始化这些值。如果初始化对象时缺少必需的成员,则会出现编译错误。.

// Initializations with required properties - valid
var p1 = new Person { Name = "Oleg", Surname = "Kyrylchuk" };
Person p2 = new("Oleg", "Kyrylchuk");

// Initializations with missing required properties - compilation error
var p3 = new Person { Name = "Oleg" };
Person p4 = new();

public class Person
{
    public Guid Id { get; set; } = Guid.NewGuid();
    public required string Name { get; set; }
    public required string Surname { get; set; }
}

如果你有多个参数化构造函数,则应SetsRequiredMembers在构造函数上添加属性,该属性会初始化所有必需的成员。它通知编译器正确的构造函数。

public class Person
{
    public Person() { }

    [SetsRequiredMembers]
    public Person(string name, string surname)
    {
        Name = name;
        Surname = surname;
    }

    public Guid Id { get; set; } = Guid.NewGuid();
    public required string Name { get; set; }
    public required string Surname { get; set; }
}

原始字符串文字

C# 11 引入了原始字符串文字。它允许包含任意文本而不转义。

格式至少为三个双引号""".."""。如果你的文本包含三个双引号,则应使用四个双引号将它们转义。

结合字符串插值,计数$表示有多少个连续的大括号开始和结束插值。在下面的示例中,我想在 JSON 中使用插值,它已经包含单个大括号{}。它会和字符串插值冲突,所以我用两个$$来表示双括号{{}}开始和结束插值。

string name = "Oleg", surname = "Kyrylchuk";

string jsonString = 
    $$"""
    {
        "Name": {{name}},
        "Surname": {{surname}}
    }
    """;

Console.WriteLine(jsonString);

UTF-8 字符串文字

C# 11 引入了 UTF-8 字符串文字。它只允许将 UTF-8 字符转换为其字节表示。转换在编译时完成。

// C# 10
byte[] array = Encoding.UTF8.GetBytes("Hello World");

// C# 11
byte[] array = "Hello World";

列出模式

C# 11 引入了列表模式。

它扩展了模式匹配以匹配数组或列表中的元素序列。你可以将列表模式与任何模式一起使用,包括常量、类型、属性和关系模式。

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

// List and constant patterns
Console.WriteLine(numbers is [1, 2, 3, 4]); // True
Console.WriteLine(numbers is [1, 2, 4]);    // False

// List and discard patterns
Console.WriteLine(numbers is [_, 2, _, 4]); // True
Console.WriteLine(numbers is [.., 3, _]);   // True

// List and logical patterns
Console.WriteLine(numbers is [_, >= 2, _, _]); // True

字符串插值表达式中的换行符

C# 11 在字符串插值中引入了换行符。

它允许 { } 之间的任何有效 C# 代码(包括换行符)来提高可读性。

在插值中使用较长的 C# 表达式时,它很有帮助,例如模式匹配开关表达式或 LINQ 查询。

// switch expression in string interpolation
int month = 5;
string season = $"The season is {month switch
{
    1 or 2 or 12 => "winter",
    > 2 and < 6 => "spring",
    > 5 and < 9 => "summer",
    > 8 and < 12 => "autumn",
    _ => "unknown. Wrong month number",
}}.";

Console.WriteLine(season);
// The season is spring.

// LINQ query in string interpolation
int[] numbers = new int[] { 1, 2, 3, 4, 5, 6 };
string message = $"The reversed even values of {nameof(numbers)} are {string.Join(", ", numbers.Where(n => n % 2 == 0)
                             .Reverse())}.";

Console.WriteLine(message);
// The reversed even values of numbers are 6, 4, 2.

自动默认结构

C# 11 编译器会自动初始化未由结构中的构造函数初始化的任何字段或属性。

下面的代码在以前的 C# 版本中无法编译。编译器设置默认值。

struct Person
{
    public Person(string name)
    {
        Name = name;
    }

    public string Name { get; set; }
    public int Age { get; set; }
}

Span<char>常量字符串的模式匹配

使用模式匹配,你可以测试字符串是否具有 C# 中的特定常量值。

C# 11 允许在常量字符串上匹配 aSpan<char>和。ReadOnlySpan<char>

ReadOnlySpan<char> str = "Oleg".AsSpan();

if (str is "Oleg")
{
    Console.WriteLine("Hey, Oleg");
}

通用属性

在 C# 中,如果要将类型传递给属性,可以使用typeof表达式。

但是,没有办法限制允许传递的类型。C# 11 允许通用属性。

class MyType { }

class GenericAttribute<T> : Attribute
    where T: MyType 
{
    private T _type;
}

[Generic<MyType>]
class MyClass { }

扩展nameof范围

C# 11 扩展了nameof表达式的范围。

你可以在方法或参数声明的属性中指定方法参数的名称。

此功能可用于添加属性以进行代码分析。

public class MyAttr : Attribute
{
    private readonly string _paramName;
    public MyAttr(string paramName)
    {
        _paramName = paramName;
    }
}
public class MyClass
{
    [MyAttr(nameof(param))]
    public void Method(int param, [MyAttr(nameof(param))] int anotherParam)
    { }
}

无符号右移运算符

C# 11 引入了无符号右移运算符>>>

它向右移动位,而不在每次移位时高位。

int n = -32;
Console.WriteLine($"Before shift: bin = {Convert.ToString(n, 2),32}, dec = {n}");

int a = n >> 2;
Console.WriteLine($"After     >>: bin = {Convert.ToString(a, 2),32}, dec = {a}");

int b = n >>> 2;
Console.WriteLine($"After    >>>: bin = {Convert.ToString(b, 2),32}, dec = {b}");

// Output:
// Before shift: bin = 11111111111111111111111111100000, dec = -32
// After     >>: bin = 11111111111111111111111111111000, dec = -8
// After    >>>: bin =   111111111111111111111111111000, dec = 1073741816

接口中的静态抽象成员

C# 11 在接口中引入了静态抽象成员。

你可以在接口中添加静态抽象成员,以定义包含可重载运算符、其他静态成员和静态属性的接口。

public interface IAdditionOperator<TSelf, TOther, TResult>
    where TSelf : IAdditionOperator<TSelf, TOther, TResult>
{
    static abstract TResult operator +(TSelf left, TOther right);
}

通用数学

添加了静态抽象成员功能以启用通用数学支持。有关它的更多信息,你可以在此博客文章(https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/)中阅读。

Point p1 = new() { X = 10, Y = 5 };
Point p2 = new() { X = 5, Y = 7 };

Point p3 = p1 + p2;
Point p4 = p1 - p2;
Console.WriteLine(p3);
Console.WriteLine(p4);

public record Point : 
    IAdditionOperators<Point, Point, Point>, 
    ISubtractionOperators<Point, Point, Point>
{
    public int X { get; set; }
    public int Y { get; set; }

    public static Point operator +(Point left, Point right)
    {
        return left with { X = left.X + right.X, Y = left.Y + right.Y };
    }

    public static Point operator -(Point left, Point right) 
    {
        return left with { X = left.X - right.X, Y = left.Y - right.Y };
    }

    public override string ToString() => $"X: {X}; Y: {Y}";