C# 7.0 向 C# 语言添加了许多新功能

01 out 变量
支持 out 参数的现有语法已在此版本中得到改进。 现在可以在方法调用的参数列表中声明 out 变量,而不是编写单独的声明语句:
if (int.TryParse(input, out int result))
Console.WriteLine(result);
else
Console.WriteLine("Could not parse input");
为清晰明了,可能需指定 out 变量的类型,如上所示。 但是,该语言支持使用隐式类型的局部变量:
if (int.TryParse(input, out var answer))
Console.WriteLine(answer);
else
Console.WriteLine("Could not parse input");
  • 代码更易于阅读。

    • 在使用 out 变量的地方声明 out 变量,而不是在上面的另一行。
  • 无需分配初始值。
    • 通过在方法调用中使用 out 变量的位置声明该变量,使得在分配它之前不可能意外使用它。
02 元组
C# 为用于说明设计意图的类和结构提供了丰富的语法。 但是,这种丰富的语法有时会需要额外的工作,但益处却很少。 你可能经常编写需要包含多个数据元素的简单结构的方法。为了支持这些方案,已将元组 添加到了 C#。 元组是包含多个字段以表示数据成员的轻量级数据结构。 这些字段没有经过验证,并且你无法定义自己的方法

低于 C# 7.0 的版本中也提供元组,但它们效率低下且不具有语言支持。 这意味着元组元素只能作为 Item1 和 Item2 等引用。 C# 7.0 引入了对元组的语言支持,可利用更有效的新元组类型向元组字段赋予语义名称。

可以通过为每个成员赋值来创建元组,并可选择为元组的每个成员提供语义名称:
(string Alpha, string Beta) namedLetters = ("a", "b");
Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}");

namedLetters 元组包含称为 Alpha 和 Beta 的字段。 这些名称仅存在于编译时且不保留,例如在运行时使用反射来检查元组时。

在进行元组赋值时,还可以指定赋值右侧的字段的名称:

var alphabetStart = (Alpha: "a", Beta: "b");
Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");

在某些时候,你可能想要解包从方法返回的元组的成员。 可通过为元组中的每个值声明单独的变量来实现此目的。 这种解包操作称为解构元组

(int max, int min) = Range(numbers);
Console.WriteLine(max);
Console.WriteLine(min);

还可以为 .NET 中的任何类型提供类似的析构。 编写 Deconstruct 方法,用作类的成员。Deconstruct 方法为你要提取的每个属性提供一组 out 参数。 考虑提供析构函数方法的此 Point 类,该方法提取 X 和 Y 坐标:

public class Point
{
public double X { get; }
public double Y { get; } public Point(double x, double y) => (X, Y) = (x, y); public void Deconstruct(out double x, out double y) => (x, y) = (X, Y);
}

可以通过向元组分配 Point 来提取各个字段:

var p = new Point(3.14, 2.71);
(double X, double Y) = p;

可在元组相关文章中深入了解有关元组的详细信息。

03 弃元

通常,在进行元组解构或使用 out 参数调用方法时,必须定义一个其值无关紧要且你不打算使用的变量。 为处理此情况,C# 增添了对弃元的支持 。 弃元是一个名为 _(下划线字符)的只写变量,可向单个变量赋予要放弃的所有值。 弃元类似于未赋值的变量;不可在代码中使用弃元(赋值语句除外)。

在以下方案中支持弃元:

  • 在对元组或用户定义的类型进行解构时。
  • 在使用 out 参数调用方法时。
  • 在使用 is 和 switch 语句匹配操作的模式中。
  • 在要将某赋值的值显式标识为弃元时用作独立标识符。

以下示例定义了 QueryCityDataForYears 方法,它返回一个包含两个不同年份的城市数据的六元组。 本例中,方法调用仅与此方法返回的两个人口值相关,因此在进行元组解构时,将元组中的其余值视为弃元。

 using System;
using System.Collections.Generic; public class Example
{
public static void Main()
{
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", , ); Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
} private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
{
int population1 = , population2 = ;
double area = ; if (name == "New York City") {
area = 468.48;
if (year1 == ) {
population1 = ;
}
if (year2 == ) {
population2 = ;
}
return (name, area, year1, population1, year2, population2);
} return ("", , , , , );
}
}
// 输出结果:
// Population change, 1960 to 2010: 393,149

有关详细信息,请参阅弃元

04 模式匹配

模式匹配 是一种可让你对除对象类型以外的属性实现方法分派的功能。 你可能已经熟悉基于对象类型的方法分派。 在面向对象的编程中,虚拟和重写方法提供语言语法来实现基于对象类型的方法分派。 基类和派生类提供不同的实现。 模式匹配表达式扩展了这一概念,以便你可以通过继承层次结构为不相关的类型和数据元素轻松实现类似的分派模式。

模式匹配支持 is 表达式和 switch 表达式。 每个表达式都允许检查对象及其属性以确定该对象是否满足所寻求的模式。 使用 when 关键字来指定模式的其他规则。

is 模式表达式扩展了常用 is 运算符以查询关于其类型的对象,并在一条指令分配结果。以下代码检查变量是否为 int,如果是,则将其添加到当前总和:

if (input is int count)
sum += count;

前面的小型示例演示了 is 表达式的增强功能。 可以针对值类型和引用类型进行测试,并且可以将成功结果分配给类型正确的新变量。

switch 匹配表达式具有常见的语法,它基于已包含在 C# 语言中的 switch 语句。 更新后的 switch 语句有几个新构造:

  • switch 表达式的控制类型不再局限于整数类型、Enum 类型、string 或与这些类型之一对应的可为 null 的类型。 可能会使用任何类型。
  • 可以在每个 case 标签中测试 switch 表达式的类型。 与 is 表达式一样,可以为该类型指定一个新变量。
  • 可以添加 when 子句以进一步测试该变量的条件。
  • case 标签的顺序现在很重要。 执行匹配的第一个分支;其他将跳过。

以下代码演示了这些新功能:

public static int SumPositiveNumbers(IEnumerable<object> sequence)
{
int sum = ;
foreach (var i in sequence)
{
switch (i)
{
case :
break;
case IEnumerable<int> childSequence:
{
foreach(var item in childSequence)
sum += (item > ) ? item : ;
break;
}
case int n when n > :
sum += n;
break;
case null:
throw new NullReferenceException("Null found in sequence");
default:
throw new InvalidOperationException("Unrecognized type");
}
}
return sum;
}
  • case 0: 是常见的常量模式。
  • case IEnumerable<int> childSequence: 是一种类型模式。
  • case int n when n > 0: 是具有附加 when 条件的类型模式。
  • case null: 是 null 模式。
  • default: 是常见的默认事例。

可以在 C# 中的模式匹配中了解有关模式匹配的更多信息。

05 Ref 局部变量和返回结果
此功能允许使用并返回对变量的引用的算法,这些变量在其他位置定义。 一个示例是使用大型矩阵并查找具有某些特征的单个位置。 下面的方法在矩阵中向该存储返回“引用” :
    /// <summary>
/// Ref局部变量和返回结果
/// </summary>
public class MatrixSearch
{
public static ref int Find(int[,] matrix, Func<int, bool> predicate)
{
for (int i = ; i < matrix.GetLength(); i++)
{
for (int j = ; j < matrix.GetLength(); j++)
{
if (predicate(matrix[i, j]))
{
return ref matrix[i, j];
}
}
}
throw new InvalidOperationException("Not found");
}
}

可以将返回值声明为 ref 并在矩阵中修改该值,如以下代码所示:

 int[,] matrix = new int[,];
ref var item = ref MatrixSearch.Find(matrix, (val) => val == );
Console.WriteLine(item);
item = ;
Console.WriteLine(matrix[, ]);

C# 语言还有多个规则,可保护你免于误用 ref 局部变量和返回结果:

  • 必须将 ref 关键字添加到方法签名和方法中的所有 return 语句中。

    • 这清楚地表明,该方法在整个方法中通过引用返回。
  • 可以将 ref return 分配给值变量或 ref 变量。
    • 调用方控制是否复制返回值。 在分配返回值时省略 ref 修饰符表示调用方需要该值的副本,而不是对存储的引用。
  • 不可向 ref 本地变量赋予标准方法返回值。
    • 因为那将禁止类似 ref int i = sequence.Count(); 这样的语句
  • 不能将 ref 返回给其生存期不超出方法执行的变量。
    • 这意味着不可返回对本地变量或对类似作用域变量的引用。
  • ref 局部变量和返回结果不可用于异步方法。
    • 编译器无法知道异步方法返回时,引用的变量是否已设置为其最终值。

添加 ref 局部变量和 ref 返回结果可通过避免复制值或多次执行取消引用操作,允许更为高效的算法。

向返回值添加 ref 是源兼容的更改。 现有代码会进行编译,但在分配时复制 ref 返回值。调用方必须将存储的返回值更新为 ref 局部变量,从而将返回值存储为引用。

有关详细信息,请参阅 ref 关键字一文。

06 本地函数

许多类的设计都包括仅从一个位置调用的方法。 这些额外的私有方法使每个方法保持小且集中。 本地函数使你能够在另一个方法的上下文内声明方法 。 本地函数使得类的阅读者更容易看到本地方法仅从声明它的上下文中调用。

对于本地函数有两个常见的用例:公共迭代器方法和公共异步方法。 这两种类型的方法都生成报告错误的时间晚于程序员期望时间的代码。 在迭代器方法中,只有在调用枚举返回的序列的代码时才会观察到任何异常。 在异步方法中,只有当返回的 Task 处于等待状态时才会观察到任何异常。 以下示例演示如何使用本地函数将参数验证与迭代器实现分离:

  public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
if (start < 'a' || start > 'z')
throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
if (end < 'a' || end > 'z')
throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); if (end <= start)
throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}"); return AlphabetSubsetImplementation(); IEnumerable<char> AlphabetSubsetImplementation()
{
for (var c = start; c < end; c++)
{
yield return c;
}
}
}

可以对 async 方法采用相同的技术,以确保在异步工作开始之前引发由参数验证引起的异常:

  public Task<string> PerformLongRunningWork(string address, int index, string name)
{
if (string.IsNullOrWhiteSpace(address))
throw new ArgumentException(message: "An address is required", paramName: nameof(address));
if (index < )
throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(message: "You must supply a name", paramName: nameof(name)); return LongRunningWorkImplementation(); async Task<string> LongRunningWorkImplementation()
{
var interimResult = await FirstWork(address);
var secondResult = await SecondStep(index, name);
return $"The results are {interimResult} and {secondResult}. Enjoy.";
}
} private async Task<string> FirstWork(string address)
{
// await ··· 业务逻辑
return "";
} private async Task<string> SecondStep(int index, string name)
{
// await ··· 业务逻辑
return "";
}

本地函数支持的某些设计也可以使用 lambda 表达式 来完成。 感兴趣的可以阅读有关差异的详细信息

07 更多的 expression-bodied 成员
C# 6 为成员函数和只读属性引入了 expression-bodied 成员。 C# 7.0 扩展了可作为表达式实现的允许的成员。 在 C# 7.0 中,你可以在属性 和索引器 上实现构造函数 、终结器 以及 get 和 set 访问器。 以下代码演示了每种情况的示例:
 public class ExpressionMembersExample
{
// Expression-bodied 构造函
public ExpressionMembersExample(string label) => this.Label = label; // Expression-bodied 终结器
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!"); private string label; // Expression-bodied get / set
public string Label
{
get => label;
set => this.label = value ?? "Default label";
}
}

本示例不需要终结器,但显示它是为了演示语法。 不应在类中实现终结器,除非有必要发布非托管资源。 还应考虑使用 SafeHandle 类,而不是直接管理非托管资源。

这些 expression-bodied 成员的新位置代表了 C# 语言的一个重要里程碑:这些功能由致力于开发开放源代码 Roslyn 项目的社区成员实现。

将方法更改为 expression bodied 成员是二进制兼容的更改

08 引发表达式

在 C# 中,throw 始终是一个语句。 因为 throw 是一个语句而非表达式,所以在某些 C# 构造中无法使用它。 它们包括条件表达式、null 合并表达式和一些 lambda 表达式。 添加 expression-bodied 成员将添加更多位置,在这些位置中,throw 表达式会很有用。 为了可以编写这些构造,C# 7.0 引入了 throw 表达式。这使得编写更多基于表达式的代码变得更容易。 不需要其他语句来进行错误检查。

从 C# 7.0 开始,throw 可以用作表达式和语句。 这允许在以前不支持的上下文中引发异常。 这些方法包括:

  • 条件运算符。 下例使用 throw 表达式在向方法传递空字符串数组时引发 ArgumentException。 在 C# 7.0 之前,此逻辑将需要显示在 if/else 语句中。

 private static void DisplayFirstNumber(string[] args)
{
string arg = args.Length >= ? args[] : throw new ArgumentException("You must supply an argument"); if (Int64.TryParse(arg, out var number))
{
Console.WriteLine($"You entered {number:F0}");
}
else
{
Console.WriteLine($"{arg} is not a number.");
} }
  • null 合并运算符。 在以下示例中,如果分配给 Name 属性的字符串为 null,则将 throw 表达式与 null 合并运算符结合使用以引发异常。
public string Name
{
get => name;
set => name = value ??
throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null");
}
DateTime ToDateTime(IFormatProvider provider) =>
throw new InvalidCastException("Conversion to a DateTime is not supported.");
09 通用的异步返回类型

从异步方法返回 Task 对象可能在某些路径中导致性能瓶颈。 Task 是引用类型,因此使用它意味着分配对象。 如果使用 async 修饰符声明的方法返回缓存结果或以同步方式完成,那么额外的分配在代码的性能关键部分可能要耗费相当长的时间。 如果这些分配发生在紧凑循环中,则成本会变高。

新语言功能意味着异步方法返回类型不限于 TaskTask<T> 和 void。 返回类型必须仍满足异步模式,这意味着 GetAwaiter 方法必须是可访问的。 作为一个具体示例,已将 ValueTask 类型添加到 .NET framework 中,以使用这一新语言功能:

 public async ValueTask<int> Func()
{
await Task.Delay();
return ;
}

需要添加 NuGet 包 System.Threading.Tasks.Extensions 才能使用 ValueTask 类型。

此增强功能对于库作者最有用,可避免在性能关键型代码中分配 Task

10 数字文本语法改进

误读的数值常量可能使第一次阅读代码时更难理解。 位掩码或其他符号值容易产生误解。C# 7.0 包括两项新功能,可用于以最可读的方式写入数字来用于预期用途:二进制文本和数字分隔符 。

在创建位掩码时,或每当数字的二进制表示形式使代码最具可读性时,以二进制形式写入该数字:

public const int Sixteen =   0b0001_0000;
public const int ThirtyTwo = 0b0010_0000;
public const int SixtyFour = 0b0100_0000;
public const int OneHundredTwentyEight = 0b1000_0000;

常量开头的 0b 表示该数字以二进制数形式写入。 二进制数可能会很长,因此通过引入 _作为数字分隔符通常更易于查看位模式,如上面二进制常量所示。 数字分隔符可以出现在常量的任何位置。 对于十进制数字,通常将其用作千位分隔符:

public const long BillionsAndBillions = 100_000_000_000;

数字分隔符也可以与 decimalfloat 和 double 类型一起使用:

public const double  AvogadroConstant = .022_140_857_747_474e23;
public const decimal GoldenRatio = .618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;

综观来说,你可以声明可读性更强的数值常量。

C#7.0 新增功能的更多相关文章

  1. C#基础拾遗系列之二:使用ILSpy探索C#7.0新增功能点

    C#基础拾遗系列之二:使用ILSpy探索C#7.0新增功能点   第一部分: C#是一种通用的,类型安全的,面向对象的编程语言.有如下特点: (1)面向对象:c# 是面向对象的范例的一个丰富实现, 它 ...

  2. C#2.0新增功能06 协变和逆变

    连载目录    [已更新最新开发文章,点击查看详细] 在 C# 中,协变和逆变能够实现数组类型.委托类型和泛型类型参数的隐式引用转换. 协变保留分配兼容性,逆变则与之相反. 以下代码演示分配兼容性.协 ...

  3. C#基础拾遗系列之二:C#7.0新增功能点

    第一部分: C#是一种通用的,类型安全的,面向对象的编程语言.有如下特点: (1)面向对象:c# 是面向对象的范例的一个丰富实现, 它包括封装.继承和多态性.C#面向对象的行为包括: 统一的类型系统 ...

  4. C#7.0新增功能点

    原文地址:  https://www.cnblogs.com/runningsmallguo/p/8972678.html 第二部分:C#7.0新增的功能 (1)数字字面量的提升: C#7中的数字文字 ...

  5. C#2.0新增功能01 分布类与分部方法

    连载目录    [已更新最新开发文章,点击查看详细] 分部类型 拆分一个类.一个结构.一个接口或一个方法的定义到两个或更多的文件中, 每个源文件包含类型或方法定义的一部分,编译应用程序时将把所有部分组 ...

  6. 说说C# 8.0 新增功能Index和Range的^0是什么?

    前言 在<C# 8.0 中使用 Index 和 Range>这篇中有人提出^0是什么意思?处于好奇就去试了,结果抛出异常.查看官方文档说^0索引与 sequence[sequence.Le ...

  7. Android 7.0 新增功能和api

    Android 7.0 Nougat 为用户和开发者引入多种新功能.本文重点介绍面向开发者的新功能. 请务必查阅 Android 7.0 行为变更以了解平台变更可能影响您的应用的领域. 要详细了解 A ...

  8. Xcode 9.0 新增功能大全

    Xcode是用于为Apple TV,Apple Watch,iPad,iPhone和Mac创建应用程序的完整开发人员工具集.Xcode开发环境采用tvOS SDK,watchOS SDK,iOS SD ...

  9. C#6.0新增功能

    C# 6.0 版本包含许多可提高开发人员工作效率的功能. 此版本中的功能包括: 只读自动属性: 可以创建只能在构造函数中设置的只读自动属性. 自动属性初始值设定项: 可以编写初始化表达式来设置自动属性 ...

  10. C#8.0 新增功能

    连载目录    [已更新最新开发文章,点击查看详细] C#8.0提供了许多增强功能 01 Readonly 成员 可将 readonly 修饰符应用于结构的任何成员. 它指示该成员不会修改状态. 这比 ...

随机推荐

  1. C# ACCESS 查询提示“至少一个参数没有被指定”问题

    错误的SQL指令如下: sqlStr = “select * from  tb_userInfo where userName=” + userName;    //错误的 sql 指令 正确的SQL ...

  2. VPS用来配置上网外,还可以做一个同步盘

    我曾经在一个活动的博文里说过,男人必须要有一个VPS和一个树莓派,VPS这个东西,以后会是中国男人的一种必备技能,今天又有一个小伙伴请教我VPS的用法,我就简单说说我目前使用的情况.首先我希望你能有点 ...

  3. Rainyday.js – Rendering Raindrops with JavaScript

    Posted · Category:GPL License,Tools 直击现场 The idea behind Rainyday.js is to create a JavaScript libra ...

  4. Windows10 下运行Linux子系统

    关于Windows10 下运行Linux子系统: Windows10内置Linux子系统初体验:http://www.jianshu.com/p/bc38ed12da1d Win10运行Ubuntu版 ...

  5. 【Linux】Linux 环境下splite以及一些正则使用

    由于在windows下,遍历目录,想查找满足条件的文件: dir /s > ..\fileresult.txt 结果得到的文件过大,999多MB的txt: split -b 10k date.f ...

  6. 30441数据定义语言DDL

    数据定义:指对数据库对象的定义.删除和修改操作. 数据库对象主要包括数据表.视图.索引等. 数据定义功能通过CREATE.ALTER.DROP语句来完成. 按照操作对象分类来介绍数据定义的SQL语法. ...

  7. .NET开发框架(一)-框架介绍与视频演示

    本文主要介绍一套基于.NET CORE的SPA高并发.高可用的开发框架. 我们暂且称它为:(让你懂.NET)开发框架. 以此为主线,陆续编写教程,讲述如何构建高并发.高可用的框架. (欢迎转载与分享) ...

  8. 解决安装Oracle本地可以访问客户端不能访问

    现象:本地需要修改监听为localhost -->win+r--> sqlplus system/123@xxdb 可以登陆,远程客户端不能登陆:需要将监听修改为IP地址,重启监听:远程可 ...

  9. jenkin+Git子模块自动拉取代码

    jenkins+Git子模块自动拉取代码 添加Git子模块 先克隆想要添加子模块的仓库git clone ssh://git@ip:port/user/project.git,这个是主目录. 进入仓库 ...

  10. CSS3常用选择器

    一.基本选择器 子元素选择器 概念:子元素选择器只能选择某元素的子元素语法格式:父元素 > 子元素 (Father > Children)兼容性:IE8+.FireFox.Chrome.S ...