C#7.0 新增功能
C# 7.0 向 C# 语言添加了许多新功能
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变量的位置声明该变量,使得在分配它之前不可能意外使用它。
- 通过在方法调用中使用
低于 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;
可在元组相关文章中深入了解有关元组的详细信息。
通常,在进行元组解构或使用 out 参数调用方法时,必须定义一个其值无关紧要且你不打算使用的变量。 为处理此情况,C# 增添了对弃元的支持 。 弃元是一个名为 _(下划线字符)的只写变量,可向单个变量赋予要放弃的所有值。 弃元类似于未赋值的变量;不可在代码中使用弃元(赋值语句除外)。
在以下方案中支持弃元:
以下示例定义了 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
有关详细信息,请参阅弃元。
模式匹配 是一种可让你对除对象类型以外的属性实现方法分派的功能。 你可能已经熟悉基于对象类型的方法分派。 在面向对象的编程中,虚拟和重写方法提供语言语法来实现基于对象类型的方法分派。 基类和派生类提供不同的实现。 模式匹配表达式扩展了这一概念,以便你可以通过继承层次结构为不相关的类型和数据元素轻松实现类似的分派模式。
模式匹配支持 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# 中的模式匹配中了解有关模式匹配的更多信息。
/// <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 关键字一文。
许多类的设计都包括仅从一个位置调用的方法。 这些额外的私有方法使每个方法保持小且集中。 本地函数使你能够在另一个方法的上下文内声明方法 。 本地函数使得类的阅读者更容易看到本地方法仅从声明它的上下文中调用。
对于本地函数有两个常见的用例:公共迭代器方法和公共异步方法。 这两种类型的方法都生成报告错误的时间晚于程序员期望时间的代码。 在迭代器方法中,只有在调用枚举返回的序列的代码时才会观察到任何异常。 在异步方法中,只有当返回的 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 表达式 来完成。 感兴趣的可以阅读有关差异的详细信息
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 成员是二进制兼容的更改。
在 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");
}
- expression-bodied lambda 或方法。 下例说明了 expression-bodied 方法,由于不支持对 DateTime 值的转换,该方法引发 InvalidCastException。
DateTime ToDateTime(IFormatProvider provider) =>
throw new InvalidCastException("Conversion to a DateTime is not supported.");
从异步方法返回 Task 对象可能在某些路径中导致性能瓶颈。 Task 是引用类型,因此使用它意味着分配对象。 如果使用 async 修饰符声明的方法返回缓存结果或以同步方式完成,那么额外的分配在代码的性能关键部分可能要耗费相当长的时间。 如果这些分配发生在紧凑循环中,则成本会变高。
新语言功能意味着异步方法返回类型不限于 Task、Task<T> 和 void。 返回类型必须仍满足异步模式,这意味着 GetAwaiter 方法必须是可访问的。 作为一个具体示例,已将 ValueTask 类型添加到 .NET framework 中,以使用这一新语言功能:
public async ValueTask<int> Func()
{
await Task.Delay();
return ;
}
需要添加 NuGet 包 System.Threading.Tasks.Extensions 才能使用 ValueTask 类型。

此增强功能对于库作者最有用,可避免在性能关键型代码中分配 Task。
误读的数值常量可能使第一次阅读代码时更难理解。 位掩码或其他符号值容易产生误解。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;
数字分隔符也可以与 decimal、float 和 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 新增功能的更多相关文章
- C#基础拾遗系列之二:使用ILSpy探索C#7.0新增功能点
C#基础拾遗系列之二:使用ILSpy探索C#7.0新增功能点 第一部分: C#是一种通用的,类型安全的,面向对象的编程语言.有如下特点: (1)面向对象:c# 是面向对象的范例的一个丰富实现, 它 ...
- C#2.0新增功能06 协变和逆变
连载目录 [已更新最新开发文章,点击查看详细] 在 C# 中,协变和逆变能够实现数组类型.委托类型和泛型类型参数的隐式引用转换. 协变保留分配兼容性,逆变则与之相反. 以下代码演示分配兼容性.协 ...
- C#基础拾遗系列之二:C#7.0新增功能点
第一部分: C#是一种通用的,类型安全的,面向对象的编程语言.有如下特点: (1)面向对象:c# 是面向对象的范例的一个丰富实现, 它包括封装.继承和多态性.C#面向对象的行为包括: 统一的类型系统 ...
- C#7.0新增功能点
原文地址: https://www.cnblogs.com/runningsmallguo/p/8972678.html 第二部分:C#7.0新增的功能 (1)数字字面量的提升: C#7中的数字文字 ...
- C#2.0新增功能01 分布类与分部方法
连载目录 [已更新最新开发文章,点击查看详细] 分部类型 拆分一个类.一个结构.一个接口或一个方法的定义到两个或更多的文件中, 每个源文件包含类型或方法定义的一部分,编译应用程序时将把所有部分组 ...
- 说说C# 8.0 新增功能Index和Range的^0是什么?
前言 在<C# 8.0 中使用 Index 和 Range>这篇中有人提出^0是什么意思?处于好奇就去试了,结果抛出异常.查看官方文档说^0索引与 sequence[sequence.Le ...
- Android 7.0 新增功能和api
Android 7.0 Nougat 为用户和开发者引入多种新功能.本文重点介绍面向开发者的新功能. 请务必查阅 Android 7.0 行为变更以了解平台变更可能影响您的应用的领域. 要详细了解 A ...
- Xcode 9.0 新增功能大全
Xcode是用于为Apple TV,Apple Watch,iPad,iPhone和Mac创建应用程序的完整开发人员工具集.Xcode开发环境采用tvOS SDK,watchOS SDK,iOS SD ...
- C#6.0新增功能
C# 6.0 版本包含许多可提高开发人员工作效率的功能. 此版本中的功能包括: 只读自动属性: 可以创建只能在构造函数中设置的只读自动属性. 自动属性初始值设定项: 可以编写初始化表达式来设置自动属性 ...
- C#8.0 新增功能
连载目录 [已更新最新开发文章,点击查看详细] C#8.0提供了许多增强功能 01 Readonly 成员 可将 readonly 修饰符应用于结构的任何成员. 它指示该成员不会修改状态. 这比 ...
随机推荐
- jquery-ui sortable 使用实例
jquery-ui sortable 使用实例 最近公司需要做任务看板,需要拖拽效果.点击查看效果.由于网站是基于vue的技术栈,最开始找了一个现成的vue封装的拖拽组件:Vue.Draggable, ...
- 浅谈网络爬虫爬js动态加载网页(一)
由于别的项目组在做舆情的预言项目,我手头正好没有什么项目,突然心血来潮想研究一下爬虫.分析的简单原型.网上查查这方面的资料还真是多,眼睛都看花了.搜了搜对于我这种新手来说,想做一个简单的爬虫程序,所以 ...
- TDD(测试驱动开发)死了吗?
01.前言 很早之前,曾在网络上见到过 TDD 这 3 个大写的英文字母,它是 Test Driven Development 这三个单词的缩写,也就是“测试驱动开发”的意思——听起来很不错的一种理念 ...
- vue+TS(CLI3)
1.用CLI3创建项目 查看当前CLI的版本,如果没有安装CLI3的 使用npm install --global vue-cli来安装CLI 安装好CLI 可以创建项目了 使用vue create ...
- HTML连载16-颜色控制属性2&标签选择器
一.颜色控制属性(上接连载15) (4)十六进制 在前端开发中通过十六进制来表示颜色,其实本质就是RGB,十六进制中是通过每两位表示一个颜色. 例如:#FFEE00,其中FF代表的是R,EE代表的G, ...
- 信鸽推送在springboot中出现jar包冲突问题
错误提示 : java.lang.NoSuchMethodError: org.json.JSONObject.put(Ljava/lang/String;Ljava/util/Collection; ...
- 记录 nginx和php安装完后的URL重写,访问空白和隐藏index.php文件的操作方法
sudo cd /etc/nginx/; sudo vi fastcgi_params; 1.URL重写 如果你的url参数不是用?xxx传递,而是自定义的,比如用/xx/xx/xx的方式传递,那么在 ...
- Python自学day-12
一.Mysql概述 RDBMS:关系型数据库管理系统.Mysql是一种RDBMS. Oracle:收费 Mysql:Oracle旗下免费 Sqlserver:微软 DB2:IBM Postgresql ...
- php中使用trait设计单例
trait Singleton { private static $instace = null; private function __construct() { } private functio ...
- 网络学习笔记(三):HTTP缓存
HTTP缓存是一种保存资源副本并在下次请求时直接使用该副本的技术,合理的使用缓存可以有效的提升web性能. 浏览器将js文件.css文件.图片等资源缓存,当下次请求这些资源时,可以不发送网络请 ...