C# 7.0 新功能

[原文发表地址]: What’s New in C# 7.0

[原文发表时间]: August 24, 2016

 

下面是对C#7.0版本所有语言功能的描述。随着Visual Studio "15" preview 4 的发布,大部分功能可以被更灵活的应用。现在正是时候将这些功能介绍给大家,你也可以借此让我们知道你的想法。

C#7.0增加了很多新的功能,更专注于数据的消费,代码的简化及代码的性能。或许最大的功能就是元组和模式匹配,它可以快速获得多个返回结果,而模式匹配,它可以根据数据的“形”的不同来简化代码。我们希望将它们结合起来,从而使你的代码更加简洁高效,也可以使你更加快乐并富有成效。您可以点击Visual studio 窗口顶部的“send feedback”按钮来告诉我们是否有哪些功能没有达到预期的功效,也可以告诉我们您对功能进一步改善的一些看法。还有许多功能没有在Preview 4 中实现。我们计划在最终版本发布下面所描述的功能,如果我们不能及时发布这些功能,会在notes上通知大家的。如果这些功能有变化,我们也会通知大家的。最终可能会有一些功能的改变和删除。

如果你对这些功能的设计过程感兴趣,你可以在Roslyn GitHub site上查看我们的设计笔记和讨论。

希望你对C#7.0有一个愉快的体验。

 

输出变量:

在当前的C#版本,使用out参数可能不像我们想的那样方便。在你调用一个带有out参数的方法之前,你必须首先声明一个变量并传递给它。你通常不会初始化这些变量(毕竟它们会被方法重写),也不能用var去声明他们,而是必须指定数据的完整类型。image

在C#7.0版本中,我们添加了out变量,可以在给一个函数传入参数的时候再去定义变量的能力。image

变量的作用域是一个封闭的块,因此后续的代码行也可以使用它们。大多数类型的语句都没有它们自己的作用域,因此被声明的out参数通常被引入到封闭的范围内。

注:在Preview 4中,适用范围规则有了更多的限制:out参数的作用域是声明它的语句。因此,上面那个例子只有在下一次发布的时候才能真正使用。

因为out变量会直接被当做out参数来声明,这样编译器通常会告诉他们应该是的类型(除非它们被重载),所以我们可以用VAR来定义,而不必使用真正的类型。image

Out参数的一种常见用法是Try…模式, 其中一个布尔返回值表示成功,out参数会携带所获得的结果:image

注:这里的i仅仅在if分支中会用到,所以Preview 4可以很好的处理这种情况。

我们还可以使用通配作为out参数,用*的形式来处理,这样你就可以不必关注你不想关注的返回值了。image

注:我们还不确定在C# 7.0是否可以使用通配符。

 

模式匹配

C# 7.0引入了模式的概念,抽象的讲,模式是语法元素,能用来测试一个数据是否具有某种“形”,并且在它被使用的时候从中提取信息。

C# 7.0中的模式示例:

  • C形式的常量模式(C是C#中的常量表达式),我们可以验证输入是否等于C
  • TX形式的类型模式(T是一种类型,X 是一个标识符)。
  • Var x形式的Var模式 (x是一个标识符)。

这仅仅是一个开始,模式是C#中的一个新语言元素,我们希望未来在C#中会有更多类型的模式。

在C# 7.0中,我们改进了两种已经存在的模式语法设计:

  • is表达式的右值也可以是模式,而不仅仅只能是一种类型。
  • switch中的case分支,也可以匹配模式,而不仅仅是常量了。

在未来,我们会增加更多模式的使用。

 

使用模式IS的表达式

下面是一个使用Is表达式的例子,用到了常量模式和类型模式。image

正如你所看到的,模式变量(模式引入的变量)很像之前说过的输出变量,可以在表达式中声明,并且在最近的定义域中使用。就像输出变量一样,模式变量是可变的。

注:像输出变量(out variables)一样,我们会在Preview 4给出更加严谨的规则。

模式和Try方法一起用也是可以的。image

 

带模式的 Switch 语句

我们正在归纳总结这些 switch 语句,因此:

  • 你可以在任何类型上使用 switch(不仅仅是原始类型)
  • 可以在 case 子句中使用模式
  • Case 子句可以拥有额外的条件

下面是一个例子image

关于新扩展的 switch 语句,有几点需要注意:

  • Case 子句的顺序现在很重要:就像 catch 子句,case 子句不再是必然不相交的,第一个子句匹配的将被选择。
  • 默认子句总是最后被判断:即使上面代码中 null 子句是最后才来,它会在默认子句被选择前被检查。这是为了与现有 switch 语义相兼容。然而,好的做法通常会让你把默认子句放到最后。
  • Switch 不会到最后的null语句:这是因为当前IS表达式的例子具有类型匹配,不会匹配到null。 这保证了空值不会不小心被任何的类型模式匹配上的情况; 你必须更明确如何处理它们(或放弃它而使用默认语句)。

 

元组

这是一个从方法中返回多个值的常见模式。目前可用的选项并不是最理想的:

输出参数。使用笨拙(即便有上面描述到的改进),它们在使用异步方法时不起作用。

  • System.Tuple<...>返回类型。使用繁琐并且需要分配一个元组对象。
  • 方法的定制传输类型:对于类型,具有大量的代码开销,其目的仅是暂时将一些值组合起来。
  • 通过动态返回类型返回匿名类型。高的性能开销,而且没有静态类型检查。

为了在这方面做得更好,C# 7.0添加了元组类型元组文字

image

这个方法可以有效地返回三个字符串,将其作为元素包含在一个元组值里。

方法的调用者将会接收到一个元组,并且可以单独地访问其中的元素:

image

Item1等是元组元素的默认名,并能够一直使用。但它们不具有描述性,因此你可以选择添加更好的。

image

现在元组的接收者有多个具有描述性的名称可用:

image

你也可以在元组文字中直接指定元素名称:

image

通常来说,你可以给元组类型分配相互无关的名字,只要独立的元素是可以被分配的,元组类型会自如转换成其他元组类型。但是也存在一些限制,特别是对于元组文字,会在常见的错误情况下警告或提示,例如偶然交换元素的名字。

注: 这些限制还没在 Preview 4 中实现 。

元组是值类型,而且他们的元素是公开的、可变的。两个元组相等(并且有相同的哈希值),意味着它们的元素都是成对匹配的(并且有相同的哈希值)。

这使得元组对于在需要多个返回值的情况下非常有用。举例来说,如果你需要一个有多种键的词典,使用元组作为你的键,一切会非常顺利。如果你需要在每个位置有多种值的列表,使用元组进行列表搜索,程序会正常运行。

注: 元组依赖于一组基本类型,它们还没包括在 Preview 4 中。为了使该特性工作,你可以通过 NuGget 获取它们:

  • 在 Solution Explorer 中右键点击项目,并选择“管理 NuGet 包…”
  • 选择“Browse”选项卡,选中“Include prerelease” ,选择“nuget.org”作为“Package source”
  • 搜索“System.ValueTuple”并安装它

 

Deconstruction解构

消耗元组的另一种方法是解构元组。解构声明是一种将元组拆分,并单独分配到新变量的语法:

image

在解构中可采用var关键字来声明单独的变量:

image

或者把var关键字提取出来放在括号外:

image

你也可以通过解构赋值来解构成一个现有变量:

image

解构不仅仅适用于元组。任何类型都可以被解构,只要它有一个对应的(实例或扩展)解构方法:

image

输出参数由结构产生的值构成。
(为何它使用了参数,而不是返回一个元组?这是为了让你可以针对不同的值拥有多个重载)。

image

构造函数和解构函数以对称方式出现是一种常见的模式。
至于输出变量,我们计划在解构中使用“通配符”,来化简你不关心的那些变量:

image

注: 现在仍然不能确定通配符是否会出现在C#7.0中。

 

局部函数

有时,一个辅助函数只在一个使用它的单一方法的内部有意义。现在你可以在其他功能体内部以一个局部函数来声明这样的函数:

image

闭合范围内的参数和局部变量在局部函数内是可用的,就像他们在lambda表达式中一样。
举个例子,迭代的方法实现通常需要一个非迭代的封装方法,以便在调用时检查实参。(迭代器本身不能启动,直到调用MoveNext才会运行)。局部函数非常适合这样的场景:

image

如果Iterator是紧随Filter的一个私有方法,它将有可能被其他成员不小心使用(不需要参数检查)。此外,它将采取所有跟Filter里相同的参数,而不是指定域内的参数。

注: 在Preview 4版本中,局部函数必须在它们被调用之前声明。这个限制将被放松,能调用读取直接赋值的局部变量。

 

文字改进

C#7.0允许用”_”作为数字分隔符:

image

你可以把它们放在任意的数字之间,以提高可读性。这对数值没有影响。
另外,C # 7引进了二进制数,因此,您可以直接指定二进制模式,而不需要知道十六进制符号。

image

 

引用返回和局部引用

就像在C#中你可以通过引用(用ref修饰符)传递参数,你现在也可以通过引用来返回参数,并通过引用将它们存储在局部变量中。

image

这个是非常有用的。例如,一个游戏可能会将它的数据保存在一个大的预分配数组结构中(为避免垃圾回收机制暂停)。这个方法可以直接返回一个引用到这样一个结构,且通过调用者可以读取和修改它。

为确保安全,也有以下一些限制:

  • 你只能返回”安全返回”的引用:一个是传递给你的引用,一个是指向对象中的引用
  • 本地引用会被初始化成一个本地存储,并且不能指向另一个存储

 

异步返回类型

到目前为止,在C#的异步方法必须返回void,Task或Task<T>。C#7.0允许以这样的方式来定义其它类型,从而使它们可以从异步方法返回。

例如,我们计划建立一个ValueTask<T>结构类型的数据。建立它是为了防止Task<T> 对象的分配时,异步操作的结果在等候是已可用。对于很多异步场景,比如以涉及缓冲为例,这可以大大减少分配的数量,并使性能有显著提升。

注: 异步返回类型尚未在Preivew4 中提供。

 

更多 Expression-bodied 成员

Expression-bodied 的方法、属性等都是C# 6.0的重大突破,但是不是所有的成员都可以使用的。 C#7.0添加了访问器、构造函数和终结器等,使更多成员可以使用Expression-bodied 方法:

image

注: 这些额外的Expression-bodied成员尚未在Preview4中提供。

这是一个由社区共享的示例,而非微软C#团队提供的,它是开源的!

 

Throw 表达式

在表达式中间抛出一个异常是很容易的:只要调用一个方法!但在C # 7.0中,我们允许在一些地方直接抛出一个表达式:

image

注: Throw表达式尚未在Preview 4中提供。