C# 11 中的新增功能
点击上方蓝字
关注我们
(本文阅读时间:11分钟)
我们很高兴地宣布 C# 11 已经发布!与往常一样,C# 开辟了一些全新的领域,同时推进了过去版本中一直在运行的几个主题。我们的文档页面上的 C# 11 的新增功能下有许多功能和详细信息,这些内容都得到了很好的介绍。
随着每个版本的发布,社区的参与度越来越高,他们贡献了从建议、见解和 bug 报告一直到整个功能实现的所有内容。这真的是每个人的C#。谢谢!
C# 11 的新增功能 https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-11?ocid=AID3052907
UTF-8字符串文字
默认情况下,C# 字符串被硬编码为 UTF-16,而互联网上流行的字符串编码是 UTF-8。为了最大限度地减少转换的麻烦和性能开销,您现在只需将 u8 后缀附加到字符串文字即可立即将它们转换为 UTF-8:
var u8 = "This is a UTF-8 string!"u8;
UTF-8 字符串文字简单地返回一个字节块——以 ReadOnlySpan<byte> 的形式。对于 UTF-8 编码很重要的场景,这可能比某些专用的新 UTF-8 字符串类型更有用。
阅读有关 UTF-8 字符串文字的文档 https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/reference-types#utf-8-string-literals?ocid=AID3052907
原始字符串文字
为什么不采用一种完全没有转义字符的字符串文字形式呢?这就是原始字符串文字。
原始字符串文字至少由三个双引号分隔:
var raw1 = """This\is\all "content"!""";
Console.WriteLine(raw1);
This\is\all "content"!
var raw2 = """""I can do ", "", """ or even """" double quotes!""""";
这使得粘贴、维护和阅读文字包含的内容变得非常容易。
多行原始字符串文字也可以截断前导空格:结束引号的位置决定了输出中开始包含空格的位置:
var raw3 = """
<element attr="content">
<body>
This line is indented by 4 spaces.
</body>
</element>
""";
// ^white space left of here is removed
<element attr="content">
<body>
This line is indented by 4 spaces.
</body>
</element>
原始字符串文字远不止于此——例如,它们支持字符串内插!
在文档中阅读有关原始字符串文字的更多信息
https://learn.microsoft.com/dotnet/csharp/programming-guide/strings/#raw-string-literals?ocid=AID3052907
对静态成员进行抽象
public interface IMonoid<TSelf> where TSelf : IMonoid<TSelf>
{
public static abstract TSelf operator +(TSelf a, TSelf b);
public static abstract TSelf Zero { get; }
}
现在任何人都可以通过为两个静态成员提供实现并将它们自己作为 TSelf 类型参数传递来实现此接口:
public struct MyInt : IMonoid<MyInt>
{
int value;
public MyInt(int i) => value = i;
public static MyInt operator +(MyInt a, MyInt b) => new MyInt(a.value + b.value);
public static MyInt Zero => new MyInt(0);
}
T AddAll<T>(params T[] elements) where T : IMonoid<T>
{
T result = T.Zero;
foreach (var element in elements)
{
result += element;
}
return result;
}
类型参数 T 受 IMonoid<T> 接口约束,这允许在 T 本身上调用该接口的静态虚拟成员——Zero 和 +!
现在我们可以用一些 MyInts 调用泛型方法,并通过类型参数传入+ 和 Zero 的正确实现:
MyInt sum = AddAll<MyInt>(new MyInt(3), new MyInt(4), new MyInt(5));
事实上,.NET 7 附带了一个新的命名空间 System.Numerics,其中充满了数学接口,表示您可能想要使用的运算符和其他静态成员的不同组合:IMonoid<T> 的“成熟”版本 接口如上。.NET 中的所有数字类型现在都实现了这些新接口——您也可以将它们添加到您自己的类型中!因此,现在很容易一劳永逸地编写数值算法——从它们处理的具体类型中抽象出来——而不是让大量的重载包含本质上相同的代码。
还值得注意的是,静态虚拟成员对于数学以外的其他事情也很有用。例如,您可以对类型层次结构的工厂方法进行抽象。但我们现在已经涵盖了足够多的内容——您可能想查看文档中关于静态抽象接口方法和泛型数学的这些教程。
即使您不使用静态虚拟成员创建接口,您也可以从现在和将来对 .NET 库所做的改进中受益。
静态抽象接口方法 https://learn.microsoft.com/dotnet/csharp/whats-new/tutorials/static-virtual-interface-members#static-abstract-interface-methods?ocid=AID3052907 泛型数学 https://learn.microsoft.com/dotnet/csharp/whats-new/tutorials/static-virtual-interface-members#generic-math?ocid=AID3052907
列表模式
C# 11 将列表模式添加到故事中。使用列表模式,您可以递归地将模式应用于类似列表的输入的单个元素——或者它们的范围。让我们直接进入上面的通用算法,使用列表模式重写为递归方法:
T AddAll<T>(params T[] elements) where T : IMonoid<T> =>
elements switch
{
[] => T.Zero,
[var first, ..var rest] => first + AddAll<T>(rest),
};
里面进行了很多事情,但在中心是一个带有两种情况的 switch 表达式。一种情况为空列表 [] 返回零,其中 Zero 由接口定义。另一种情况使用 var first 模式将第一个元素提取到 first 中,然后使用 .. 将剩余元素提取到 rest 中,以将所有剩余元素切出到 var rest 模式中。
在文档中阅读有关列表模式的更多信息。 https://learn.microsoft.com/dotnet/csharp/language-reference/operators/patterns#list-patterns?ocid=AID3052907
必需的成员
在创建使用对象初始值设定项的类型时,您过去无法指定必须初始化某些属性。现在,您可以说属性或字段是必需的。这意味着当创建该类型的对象时,它必须由对象初始值设定项初始化:
public class Person
{
public required string FirstName { get; init; }
public string? MiddleName { get; init; }
public required string LastName { get; init; }
}
现在在没有初始化两个必需属性的情况下创建一个 Person 是错误的:
var person = new Person { FirstName = "Ada" }; // Error: no LastName!
查看文档以获取有关所需成员的更多信息 https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/required?ocid=AID3052907
写在最后
C# 11 还包括许多其他功能。我希望这道“开胃菜”能激发您探索 C# 11 中的新功能,并希望您在使用 C# 11 进行编码时获得与我们编写它时一样多的乐趣!我们努力使语言变得更有用,不仅通过增加更多的表达能力(如静态虚拟成员),而且通过简化、精简和删除样板(如原始字符串文字和列表模式)并使其更安全(如与必需的成员)。
*未经授权请勿私自转载此文章及图片。
点击「阅读原文」了解更多 ~