C#7.0 于 2017年3月 随 .NET 4.7 和 VS2017 发布。
一. out 变量(out variables)
以前我们使用out变量必须在使用前进行声明,C# 7.0 给我们提供了一种更简洁的语法 “使用时进行内联声明” 。如下所示:
var input = ReadLine(); if (int.TryParse(input, out var result)) { WriteLine("您输入的数字是:{0}", result); } else { WriteLine("无法解析输入..."); }
上面代码编译后:
int num; string s = Console.ReadLine(); if (int.TryParse(s, out num)) { Console.WriteLine("您输入的数字是:{0}", num); } else { Console.WriteLine("无法解析输入..."); }
原理解析:所谓的 “内联声明” 编译后就是以前的原始写法,只是现在由编译器来完成。
备注:在进行内联声明时,即可直接写明变量的类型也可以写隐式类型,因为out关键字修饰的一定是局部变量。
二. 元组(Tuples)
详见:C#(99):C# 7.0-7.3 新特性Tuple元组 、 ValueTuple 值元组详解 - springsnow - 博客园 (cnblogs.com)
三. 模式匹配(Pattern matching)
1. is 表达式(is expressions)
如:
static int GetSum(IEnumerable<object> values) { var sum = 0; if (values == null) return sum; foreach (var item in values) { if (item is short) // C# 7 之前的 is expressions { sum += (short)item; } else if (item is int val) // C# 7 的 is expressions { sum += val; } else if (item is string str && int.TryParse(str, out var result)) // is expressions 和 out variables 结合使用 { sum += result; } else if (item is IEnumerable<object> subList) { sum += GetSum(subList); } } return sum; }
使用方法:
条件控制语句(obj is type variable) { // Processing... }
原理解析:此 is 非彼 is ,这个扩展的 is 其实是 as 和 if 的组合。即它先进行 as 转换再进行 if 判断,判断其结果是否为 null,不等于 null 则执行
语句块逻辑,反之不行。由上可知其实C# 7之前我们也可实现类似的功能,只是写法上比较繁琐。
2. switch语句更新(switch statement updates)
如:
static int GetSum(IEnumerable<object> values) { var sum = 0; if (values == null) return 0; foreach (var item in values) { switch (item) { case 0: // 常量模式匹配 break; case short sval: // 类型模式匹配 sum += sval; break; case int ival: sum += ival; break; case string str when int.TryParse(str, out var result): // 类型模式匹配 + 条件表达式 sum += result; break; case IEnumerable<object> subList when subList.Any(): sum += GetSum(subList); break; default: throw new InvalidOperationException("未知的类型"); } } return sum; }
使用方法:
switch (item) { case type variable1: // processing... break; case type variable2 when predicate: // processing... break; default: // processing... break; }
原理解析:此 switch 非彼 switch,编译后你会发现扩展的 switch 就是 as 、if 、goto 语句的组合体。同 is expressions 一样,以前我们也能实
现只是写法比较繁琐并且可读性不强。
总结:模式匹配语法是想让我们在简单的情况下实现类似与多态一样的动态调用,即在运行时确定成员类型和调用具体的实现。
四. 局部引用和引用返回 (Ref locals and returns)
我们知道 C# 的 ref 和 out 关键字是对值传递的一个补充,是为了防止值类型大对象在Copy过程中损失更多的性能。现在在C# 7中 ref 关键字得
到了加强,它不仅可以获取值类型的引用而且还可以获取某个变量(引用类型)的局部引用。如:
static ref int GetLocalRef(int[,] arr, Func<int, bool> func) { for (int i = 0; i < arr.GetLength(0); i++) { for (int j = 0; j < arr.GetLength(1); j++) { if (func(arr[i, j])) { return ref arr[i, j]; } } } throw new InvalidOperationException("Not found"); }
使用方法:
int[,] arr = { { 10, 15 }, { 20, 25 } }; ref var num = ref GetLocalRef(arr, c => c == 20); num = 600; Console.WriteLine(arr[1, 0]);
Print results:
使用方法:
1. 方法的返回值必须是引用返回:
a) 声明方法签名时必须在返回类型前加上 ref 修饰。
b) 在每个 return 关键字后也要加上 ref 修饰,以表明是返回引用。
2. 分配引用(即赋值),必须在声明局部变量前加上 ref 修饰,以及在方法返回引用前加上 ref 修饰。
注:C# 开发的是托管代码,所以一般不希望程序员去操作指针。并由上述可知在使用过程中需要大量的使用 ref 来标明这是引用变量(编译后其
实没那么多),当然这也是为了提高代码的可读性。
总结:虽然 C# 7 中提供了局部引用和引用返回,但为了防止滥用所以也有诸多约束,如:
1. 你不能将一个值分配给 ref 变量,如:
ref int num = 10; // error:无法使用值初始化按引用变量
2. 你不能返回一个生存期不超过方法作用域的变量引用,如:
public ref int GetLocalRef(int num) => ref num; // error: 无法按引用返回参数,因为它不是 ref 或 out 参数
3. ref 不能修饰 “属性” 和 “索引器”。
var list = new List<int>(); ref var n = ref list.Count; // error: 属性或索引器不能作为 out 或 ref 参数传递
原理解析:非常简单就是指针传递,并且个人觉得此语法的使用场景非常有限,都是用来处理大对象的,目的是减少GC提高性能。
五. 局部函数(Local functions)
C# 7 中的一个功能“局部函数”,如下所示:
static IEnumerable<char> GetCharList(string str) { if (IsNullOrWhiteSpace(str)) throw new ArgumentNullException(nameof(str)); return GetList(); IEnumerable<char> GetList() { for (int i = 0; i < str.Length; i++) { yield return str[i]; } } }
使用方法:
[数据类型,void] 方法名([参数]) { // Method body;[] 里面都是可选项 }
原理解析:局部函数虽然是在其他函数内部声明,但它编译后就是一个被 internal 修饰的静态函数,它是属于类,至于它为什么能够使用上级函
数中的局部变量和参数呢?那是因为编译器会根据其使用的成员生成一个新类型(Class/Struct)然后将其传入函数中。由上可知则局部函数的声
明跟位置无关,并可无限嵌套。
总结:个人觉得局部函数是对 C# 异常机制在语义上的一次补充(如上例),以及为代码提供清晰的结构而设置的语法。但局部函数也有其缺点,
就是局部函数中的代码无法复用(反射除外)。
六. 更多的表达式体成员(More expression-bodied members)
C# 6 的时候就支持表达式体成员,但当时只支持“函数成员”和“只读属性”,这一特性在C# 7中得到了扩展,它能支持更多的成员:构造函数、析构函数、带 get,set 访问器的属性、以及索引器。如下所示:
public class Student { private string _name; // Expression-bodied 构造函数 public Student(string name) => _name = name; // Expression-bodied 析构函数 ~Student() => Console.WriteLine("Finalized!"); // Expression-bodied 属性访问器
public string Name { get => _name; set => _name = value ?? "Mike"; } // Expression-bodied 索引器
public string this[string name] => Convert.ToBase64String(Encoding.UTF8.GetBytes(name)); }
备注:索引器其实在C# 6中就得到了支持,但其它三种在C# 6中未得到支持。
七. Throw 表达式(Throw expressions)
异常机制是C#的重要组成部分,但在以前并不是所有语句都可以抛出异常的,如:条件表达式(? :)、null合并运算符(??)、一些Lambda表达式。而使用 C# 7 您可在任意地方抛出异常。如:
public class Student { private string _name = GetName() ?? throw new ArgumentNullException(nameof(GetName)); private int _age; public int Age { get => _age; set => _age = value <= 0 || value >= 130 ? throw new ArgumentException("参数不合法") : value; } static string GetName() => null; }
八. 扩展异步返回类型(Generalized async return types)
在之前我们想用“async”、“await”就必须使用Task作为返回值(void特殊情况忽略),但Task是一个引用类型(class),这样在非常简单的任务中会造成浪费(内存和gc)。
以前异步的返回类型必须是:Task、Task<T>、void,现在 C# 7 中新增了一种类型:ValueTask<T>,如下所示:
public async ValueTask<int> Func() { await Task.Delay(3000); return 100; }
总结:ValueTask<T> 与 ValueTuple 非常相似,所以就不列举: ValueTask<T> 与 Task 之间的异同了,但它们都是为了优化特定场景性能而
新增的类型。
使用 ValueTask<T> 则需要导入: Install - Package System.Threading.Tasks.Extensions
九. 数字文本语法的改进(Numeric literal syntax improvements)
C# 7 还包含两个新特性:二进制文字、数字分隔符,如下所示:
var one = 0b0001; var sixteen = 0b0001_0000; long salary = 1000_000_000; decimal pi = 3.141_592_653_589m;
注:二进制文本是以0b(零b)开头,字母不区分大小写;数字分隔符只有三个地方不能写:开头,结尾,小数点前后。
总结:二进制文本,数字分隔符 可使常量值更具可读性。