原文 | Kathleen
翻译 | 郑子铭
Visual Studio 17.1(Visual Studio 2022 Update 1)和 .NET SDK 6.0.200 包含 C# 11 的预览功能!您可以更新 Visual Studio 或下载最新的 .NET SDK 来获得这些功能。
查看帖子 Visual Studio 2022 17.1 现已推出!了解 Visual Studio 中的新增功能和发布 .NET 7 Preview 1 的帖子以了解更多 .NET 7 预览功能。
设计 C# 11
我们喜欢公开设计和开发!您可以在 CSharpLang 存储库中找到有关未来 C# 功能的建议和语言设计会议的注释。主页解释了我们的设计过程,您可以在 .NET Community Runtime and Languages Standup 上收听 Mads Torgersen,他在其中谈到了设计过程。
一旦计划好某个功能的工作,工作和跟踪就会转移到 Roslyn 存储库。您可以在功能状态页面上找到即将推出的功能的状态。您可以看到我们正在进行的工作以及合并到每个预览中的内容。您还可以回顾以前的版本以检查您可能忽略的功能。
在这篇文章中,我将这些有时是复杂的技术性讨论提炼成代码中每个特性的含义。
我们希望您能试用这些新的预览功能,并让我们知道您的想法。要试用 C# 11 预览功能,请创建一个 C# 项目并将 LangVersion 设置为 Preview。您的 .csproj 文件可能如下所示:
代码语言:javascript复制<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 预览:允许在插值字符串的“孔”中换行
在提案中阅读有关此更改的更多信息,删除非逐字插值字符串中的插值不能包含换行符的限制。 #4935
C# 支持两种类型的内插字符串:逐字和非逐字内插字符串(分别为 $@"" 和 $"")。它们之间的一个关键区别是非逐字插值字符串不能在其文本段中包含换行符,而必须使用转义符(如 rn)。逐字插值字符串可以在其文本段中包含换行符,并且不会转义换行符或其他字符(除了“”来转义引号本身)。所有这些行为保持不变。
以前,这些限制扩展到非逐字插值字符串的孔。孔是表示插值表达式的简写方式,是花括号内提供运行时值的部分。孔本身不是文本,不应遵守内插字符串文本段的转义/换行规则。
例如,以下内容会导致 C# 10 中的编译器错误,并且在此 C# 11 预览版中是合法的:
代码语言:javascript复制var v = $"Count ist: { this.Is.Really.Something()
.That.I.Should(
be able)[
to.Wrap()] }.";
C# 11 预览:列表模式
阅读更多关于提案列表模式中的这种变化。
新的列表模式允许您匹配列表和数组。您可以匹配元素,并且可以选择包含匹配零个或多个元素的切片模式。使用切片模式,您可以丢弃或捕获零个或多个元素。
列表模式的语法是方括号括起来的值,切片模式是两个点。切片模式后面可以跟另一个列表模式,例如 var 模式来捕获切片的内容。
模式 [1, 2, .., 10] 匹配以下所有内容:
代码语言:javascript复制int[] arr1 = { 1, 2, 10 };
int[] arr1 = { 1, 2, 5, 10 };
int[] arr1 = { 1, 2, 5, 6, 7, 8, 9, 10 };
要探索列表模式,请考虑:
代码语言:javascript复制public static int CheckSwitch(int[] values)
=> values switch
{
[1, 2, .., 10] => 1,
[1, 2] => 2,
[1, _] => 3,
[1, ..] => 4,
[..] => 50
};
当它传递以下数组时,结果如下所示:
代码语言:javascript复制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
您还可以捕获切片模式的结果:
代码语言:javascript复制public static string CaptureSlice(int[] values)
=> values switch
{
[1, .. var middle, _] => $"Middle {String.Join(", ", middle)}",
[.. var all] => $"All {String.Join(", ", all)}"
};
列表模式适用于任何可数和可索引的类型——这意味着它具有可访问的 Length 或 Count 属性,并且具有 int 或 System.Index 参数的索引器。切片模式适用于任何可数和可切片的类型——这意味着它具有一个可访问的索引器,该索引器将 Range 作为参数,或者具有一个具有两个 int 参数的可访问的 Slice 方法。
我们正在考虑在 IEnumerable 类型上添加对列表模式的支持。如果您有机会使用此功能,请告诉我们您对此的想法。
C# 11 预览:参数空值检查
在提案参数空检查中阅读有关此更改的更多信息。
我们将此功能放入此早期预览版中,以确保我们有时间获得反馈。已经讨论过一种非常简洁的语法与一种更冗长的语法。我们希望获得客户反馈以及有机会尝试此功能的用户。
使用样板代码的变体来验证方法参数是否为空是很常见的,例如:
代码语言:javascript复制public static void M(string s)
{
if (s is null)
{
throw new ArgumentNullException(nameof(s));
}
// Body of the method
}
使用参数空检查,您可以通过添加 !! 到参数名称来缩写您的意图:
代码语言:javascript复制public static void M(string s!!)
{
// Body of the method
}
将生成代码以执行空值检查。生成的空值检查将在方法中的任何代码之前执行。对于构造函数,空值检查发生在字段初始化、调用基构造函数和调用 this 构造函数之前。
此功能独立于可空引用类型 (NRT),尽管它们可以很好地协同工作。 NRT 可帮助您在设计时了解 null 是否可能。参数空值检查可以更轻松地在运行时检查空值是否已传递给您的代码。当您的代码与可能未启用 NRT 的外部代码交互时,这一点尤其重要。
检查和 if (param is null) throw new ArgumentNullException(...) 是等效的。当多个参数包含 !! 运算符,则检查将按照声明参数的顺序进行。
下面是一些 !! 在哪里可以使用的限制规则:
- 只有在有实现时才能将空检查应用于参数。例如,抽象方法参数不能使用 !!。其他不能使用的情况包括:
- 外部方法参数。
- 委托参数。
- 当方法不是默认接口方法 (DIM) 时的接口方法参数。
- 空值检查只能应用于可以检查的参数。
根据第二条规则排除的场景示例是丢弃和输出参数。可以对 ref 和 in 参数进行空值检查。
允许对索引器参数进行空检查,并将检查添加到 get 和 set 访问器。例如:
代码语言:javascript复制public string this[string key!!] { get { ... } set { ... } }
Null-checks 可以用于 lambda 参数,无论它们是否被括号括起来:
代码语言:javascript复制// An identity lambda which throws on a null input
Func<string, string> s = x!! => x;
异步方法可以有空检查参数。调用方法时会发生空值检查。
该语法对迭代器方法的参数也有效。调用迭代器方法时会发生空值检查,而不是遍历底层枚举器时。这适用于传统或异步迭代器:
代码语言:javascript复制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);
}
}
与可空引用类型的交互
任何具有 !! 的参数应用于其名称的运算符将以可空状态为非空开始。即使参数本身的类型可能为 null,也是如此。这可能发生在显式可为空的类型(例如字符串?)或不受约束的类型参数中。
当 !!参数上的语法与参数上的显式可空类型相结合,编译器将发出警告:
代码语言:javascript复制void WarnCase<T>(
string? name!!, // CS8995 Nullable type 'string?' is null-checked and will throw if null.
T value1!! // Okay
)
构造函数
当您从代码中的显式空检查更改为使用空验证语法 (!!) 进行空检查时,会有一个很小但可以观察到的变化。您的显式验证发生在使用 this 调用的字段初始值设定项、基类构造函数和构造函数之后。使用参数空检查语法执行的空检查将在任何这些执行之前发生。早期的测试人员发现这个顺序很有帮助,我们认为这种差异很少会对代码产生不利影响。但在从显式空检查转移到新语法之前,请检查它是否不会影响您的程序。
设计注意事项
您可以听到 Jared Parsons 在 2022 年 2 月 9 日的 .NET 语言和运行时社区站会中的演讲。当 Jared 加入我们的行列时,该剪辑开始了大约 45 分钟,更多地讨论了将这个功能引入预览的决定,并做出了回应一些常见的反馈。
有些人在看到 PR 在 .NET 运行时使用此功能时了解了此功能。 Microsoft 的其他团队提供了有关 C# 的重要 dogfooding 反馈。得知 .NET 运行时使用这种新的空检查语法删除了近 20,000 行代码,这令人兴奋。
在参数名称上的语法是 !!。它在名称上,而不是类型上,因为这是在您的代码中如何处理该特定参数的一个特征。我们决定不使用属性是因为它会如何影响代码的可读性,并且因为属性很少会像此功能那样影响程序的执行方式。
我们考虑并拒绝了对所有可空参数进行空检查的全局设置。参数空值检查强制设计选择如何处理空值。有许多方法,其中 null 参数是有效值。在类型不为 null 的任何地方都这样做会过度,并且会对性能产生影响。仅限制于易受 null 影响的方法(例如公共接口)将是极其困难的。我们还从 .NET 运行时工作中了解到,有很多地方不适合进行检查,因此需要按参数选择退出机制。我们目前认为运行时空值检查的全局方法可能不合适,如果我们考虑使用全局方法,那将是一个不同的特性。
总结
Visual Studio 17.1 和 .NET SDK 6.0.200 提供了对 C# 11 的早期了解。您可以在插值字符串的花括号(孔)内使用参数空检查、列表模式和新行。
我们希望您通过更新 Visual Studio 或下载最新的 .NET SDK,然后将 LangVersion 设置为预览来查看 C# 11 预览功能。
我们期待听到您的想法,在这里或通过 GitHub 上的 CSharpLang 存储库中的讨论!
原文链接
Early peek at C# 11 features