最近,Visual Studio 2019 Preview 2发行了,并提供了一些其他的C#8.0功能供您试用。 这主要是与样本的比较,尽管最后我将介绍其他新闻和更改。
本文为英文。

感谢您翻译我们的MSP
Lev Bulanov 。
在更多地方有更多图案
当C#7.0中出现模式匹配时,我们注意到,预计将来更多地方的模式数量会增加。 这次到了! 我们将添加所谓的递归模式,以及一种更紧凑的开关表达式形式(您猜对了)。
首先,这是C#7.0模式的简单示例:
class Point { public int X { get; } public int Y { get; } public Point(int x, int y) => (X, Y) = (x, y); public void Deconstruct(out int x, out int y) => (x, y) = (X, Y); } static string Display(object o) { switch (o) { case Point p when pX == 0 && pY == 0: return "origin"; case Point p: return $"({pX}, {pY})"; default: return "unknown"; } }
切换表达式
首先,请注意,实际上,许多switch表达式在case主体中并没有做很多有趣的工作。 通常,它们都可以简单地创建一个值,方法是将其分配给变量或将其返回(如上所示)。 在所有这些情况下,开关似乎都不合适。 这类似于五十年前的语言功能。
我们认为是时候添加一个switch语句表单了。 它适用于以下示例:
static string Display(object o) { return o switch { Point p when pX == 0 && pY == 0 => "origin", Point p => $"({pX}, {pY})", _ => "unknown" }; }
与switch语句相比,有些变化。 让我们列出它们:
- switch关键字是测试值和{...}案例列表之间的“中缀”。 这使得它与其他表达式更加组合,并且更容易从视觉上与switch语句区分开。
- case关键字和符号:简短地由lambda箭头=>代替。
- 为简洁起见, 默认值已由_重置模式代替。
- 身体是表情。 所选正文的结果成为switch语句的结果。
因为表达式必须重要或引发异常,所以以不匹配结尾的选择表达式将引发异常。 发生这种情况时,编译器会警告您,但不会强制您以catch-all函数结束所有select语句。
由于我们的Display方法现在由单个return语句组成,因此我们可以简化其表达方式:
static string Display(object o) => o switch { Point p when pX == 0 && pY == 0 => "origin", Point p => $"({pX}, {pY})", _ => "unknown" };
无论给出什么格式建议,它们都应该非常清楚和简洁。 如上所述,Briefity允许您以“表格”方式设置开关的格式,将模式和bod放在同一行,而=>则在下面对齐。
顺便说一句,我们计划根据C#中的所有其他“大括号括起来的列表”,允许在最后一种情况之后使用逗号,但是在Preview 2中尚不允许这样做。
图案属性
说起简洁,模式突然成为选择表达中最难的元素。 让我们做些事情。
请注意,select表达式使用Point p类型(两次)的模式,以及在第一种情况下 何时添加其他条件。
在C#8.0中,我们向模式类型添加了其他可选元素,这允许模式本身更深入地映射到模式。 您可以通过添加包含嵌套模式的{...}并将其应用于可用属性或字段来将其设置为属性模式。 这使我们可以如下重写switch表达式:
static string Display(object o) => o switch { Point { X: 0, Y: 0 } p => "origin", Point { X: var x, Y: var y } p => $"({x}, {y})", _ => "unknown" };
两种情况仍然验证o是Point 。 在第一种情况下,将常量0的模式递归应用于变量p的属性X和Y ,检查它们是否具有此值。 因此,我们可以消除这种情况以及其他类似情况下的when条件。
在第二种情况下,将var模式应用于X和Y中的每个。 回想一下,C#7.0中的var模式始终会成功,并且只需声明一个新变量即可保存该值。 因此x和y包含pX和pY的 int值。
我们从不使用p ,实际上可以在这里跳过它:
Point { X: 0, Y: 0 } => "origin", Point { X: var x, Y: var y } => $"({x}, {y})", _ => "unknown"
对于所有类型的模式(包括属性模式),一件事保持不变,这是要求该值必须为非零的要求。 这提供了将“空”属性{}模式用作紧凑的“非零”模式的可能性。 举个例子 我们可以用以下两种情况替换后备:
{} => o.ToString(), null => "null"
{}处理剩余的非零对象,并且null获得零,因此切换是详尽的,并且编译器将不会抱怨缺少值。
位置模式
属性模式不会缩短第二个Point的情况。 无需担心,您可以做更多。
请注意, Point类具有Deconstruct方法,即所谓的deconstructor。 在C#7.0中,解构函数允许您在分配值时“解构”一个值,因此您可以编写,例如:
(int x, int y) = GetPoint();
C#7.0没有将解构与模式集成在一起。 这随位置模式而变化,这是在C#8.0中扩展模式类型的另一种方式。 如果匹配类型是元组类型或具有解构函数,则可以使用位置模式作为应用递归模式的紧凑方法,而不必命名属性:
static string Display(object o) => o switch { Point(0, 0) => "origin", Point(var x, var y) => $"({x}, {y})", _ => "unknown" };
在将对象与Point匹配之后,将应用解构函数,并将嵌套模式应用于结果值。
解构函数并不总是合适的。 仅应将它们添加到那些确实清楚哪个值是哪个值的类型中。 例如,对于Point类,您可以假定第一个值为X ,第二个值为Y ,因此上述开关表达式清晰易懂。
元组模式
位置模式的一个非常有用的特殊情况是它们在元组中的应用。 如果将switch语句直接应用于元组表达式,我们甚至可以省略括号的附加集合,如switch(x,y,z)而不是switch((x,y,z)) 。
元组模式非常适合同时测试多个输入。 这是状态机的简单实现:
static State ChangeState(State current, Transition transition, bool hasKey) => (current, transition) switch { (Opened, Close) => Closed, (Closed, Open) => Opened, (Closed, Lock) when hasKey => Locked, (Locked, Unlock) when hasKey => Closed, _ => throw new InvalidOperationException($"Invalid transition") };
当然,我们可以在元组中包含hasKey而不是使用when子句-这是一个问题:
static State ChangeState(State current, Transition transition, bool hasKey) => (current, transition, hasKey) switch { (Opened, Close, _) => Closed, (Closed, Open, _) => Opened, (Closed, Lock, true) => Locked, (Locked, Unlock, true) => Closed, _ => throw new InvalidOperationException($"Invalid transition") };
通常,您会看到递归模式和switch表达式可以导致更清晰和更具声明性的程序逻辑。
预览2中C#8.0的其他功能
尽管在VS 2019 Preview 2中使用模式的主要功能是最重要的事实,但我希望有一些较小的功能也会有用且有趣。 我将不做任何详细说明,仅对它们进行简要说明。
使用广告
在C# 使用中,运算符始终会增加嵌套级别,这可能会很烦人并且可读性很差。 在简单的情况下,当您只需要清除作用域末尾的资源时,可以使用using声明。 using声明只是局部变量的声明,其前面带有using关键字,其内容位于当前指令块的末尾。 因此,代替:
static void Main(string[] args) { using (var options = Parse(args)) { if (options["verbose"]) { WriteLine("Logging..."); } ... }
你可以写
static void Main(string[] args) { using var options = Parse(args); if (options["verbose"]) { WriteLine("Logging..."); } }
一次性引用结构
引用结构在C#7.2中引入,在这里似乎没有重复的地方。 但仍然值得注意的是:它们有一些局限性,例如无法实现接口。 现在可以在不实现IDisposable接口的情况下使用引用结构,只需在其中使用Dispose方法即可。
静态局部功能
如果要确保您的本地函数不会招致与从范围“捕获”(引用)变量相关的运行时开销,则可以将其声明为静态。 然后,编译器将阻止链接到封闭函数中声明的所有内容-除了其他静态局部函数!
对预览版1的更改
预览1的主要功能是可为空的引用类型和异步流。 预览2中的这两项功能都有所改变,因此,如果您开始使用它们,了解以下内容将很有用。
可空引用类型
我们在源头(通过#nullable和#pragma警告指令)和项目级别添加了更多用于管理可空警告的选项。 我们还将对项目文件的预订更改为<NullableContextOptions> enable </ NullableContextOptions> 。
异步线程
我们更改了编译器期望的IAsyncEnumerable <T>接口的形式。 这导致编译器不与.NET Core 3.0 Preview 1中提供的接口同步的事实,这可能会引起一些问题。 但是,.NET Core 3.0 Preview 2即将发布,它将返回同步。