作者:雨夜潇湘
原文:www.cnblogs.com/VVStudy/p/6551300.html(点击文末阅读原文即可前往)
1. out 变量(out variables)
以前我们使用out变量必须在使用前进行声明,C# 7.0 给我们提供了一种更简洁的语法 “使用时进行内联声明” 。如下所示:
1 var input = ReadLine();
2 if (int.TryParse(input, out var result))
3 {
4 WriteLine("您输入的数字是:{0}",result);
5 }
6 else
7 {
8 WriteLine("无法解析输入...");
9 }
上面代码编译后:
1 int num;
2 string s = Console.ReadLine();
3 if (int.TryParse(s, out num))
4 {
5 Console.WriteLine("您输入的数字是:{0}", num);
6 }
7 else
8 {
9 Console.WriteLine("无法解析输入...");
10 }
原理解析:所谓的 “内联声明” 编译后就是以前的原始写法,只是现在由编译器来完成。
备注:在进行内联声明时,即可直接写明变量的类型也可以写隐式类型,因为out关键字修饰的一定是局部变量。
2. 元组(Tuples)
元组(Tuple)在 .Net 4.0 的时候就有了,但元组也有些缺点,如:
1)Tuple 会影响代码的可读性,因为它的属性名都是:Item1,Item2.. 。
2)Tuple 还不够轻量级,因为它是引用类型(Class)。
备注:上述所指 Tuple 还不够轻量级,是从某种意义上来说的或者是一种假设,即假设分配操作非常的多。
C# 7 中的元组(ValueTuple)解决了上述两个缺点:
1)ValueTuple 支持语义上的字段命名。
2)ValueTuple 是值类型(Struct)。
1. 如何创建一个元组?
var tuple = (1, 2); // 使用语法糖创建元组
var tuple2 = ValueTuple.Create(1, 2); // 使用静态方法【Create】创建元组
var tuple3 = new ValueTuple<int, int>(1, 2); // 使用 new 运算符创建元组
WriteLine($"first:{tuple.Item1}, second:{tuple.Item2}, 上面三种方式都是等价的。");
原理解析:上面三种方式最终都是使用 new 运算符来创建实例。
2. 如何创建给字段命名的元组?
1 // 左边指定字段名称
2 (int one, int two) tuple = (1, 2);
3 WriteLine($"first:{tuple.one}, second:{tuple.two}");
4
5 // 右边指定字段名称
6 var tuple2 = (one: 1, two: 2);
7 WriteLine($"first:{tuple2.one}, second:{tuple2.two}");
8
9 // 左右两边同时指定字段名称
10 (int one, int two) tuple3 = (first: 1, second: 2); /* 此处会有警告:由于目标类型(xx)已指定了其它名称,因为忽略元组名称xxx */
11 WriteLine($"first:{tuple3.one}, second:{tuple3.two}");
注:左右两边同时指定字段名称,会使用左边的字段名称覆盖右边的字段名称(一一对应)。
原理解析:上述给字段命名的元组在编译后其字段名称还是:Item1, Item2...,即:“命名”只是语义上的命名。
3. 什么是解构?
解构顾名思义就是将整体分解成部分。
4. 解构元组,如下所示:
1 var (one, two) = GetTuple();
2
3 WriteLine($"first:{one}, second:{two}");
1 static (int, int) GetTuple()
2 {
3 return (1, 2);
4 }
原理解析:解构元组就是将元组中的字段值赋值给声明的局部变量(编译后可查看)。
备注:在解构时“=”左边能提取变量的数据类型(如上所示),元组中字段类型相同时即可提取具体类型也可以是隐式类型,但元组中字段类型不相同时只能提取隐式类型。
5. 解构可以应用于 .Net 的任意类型,但需要编写 Deconstruct方法成员(实例或扩展)。如下所示:
1 public class Student
2 {
3 public Student(string name, int age)
4 {
5 Name = name;
6 Age = age;
7 }
8
9 public string Name { get; set; }
10
11 public int Age { get; set; }
12
13 public void Deconstruct(out string name, out int age)
14 {
15 name = Name;
16 age = Age;
17 }
18 }
使用方式如下:
1 var (Name, Age) = new Student("Mike", 30);
2
3 WriteLine($"name:{Name}, age:{Age}");
原理解析:编译后就是由其实例调用 Deconstruct 方法,然后给局部变量赋值。
Deconstruct 方法签名:
1
// 实例签名
2 public void Deconstruct(out type variable1, out type variable2...)
3
4 // 扩展签名
5 public static void Deconstruct(this type instance, out type variable1, out type variable2...)
总结:1. 元组的原理是利用了成员类型的嵌套或者是说成员类型的递归。2. 编译器很牛B才能提供如此优美的语法。
使用 ValueTuple 则需要导入: Install - Package System.ValueTuple
3. 模式匹配(Pattern matching)
1. is 表达式(is expressions),如:
1 static int GetSum(IEnumerable<object> values)
2 {
3 var sum = 0;
4 if (values == null) return sum;
5
6 foreach (var item in values)
7 {
8 if (item is short) // C# 7 之前的 is expressions
9 {
10 sum += (short)item;
11 }
12 else if (item is int val) // C# 7 的 is expressions
13 {
14 sum += val;
15 }
16 else if (item is string str && int.TryParse(str, out var result)) // is expressions 和 out variables 结合使用
17 {
18 sum += result;
19 }
20 else if (item is IEnumerable<object> subList)
21 {
22 sum += GetSum(subList);
23 }
24 }
25
26 return sum;
27 }
使用方法:
1 条件控制语句(obj is type variable)
2 {
3 // Processing...
4 }
原理解析:此 is 非彼 is ,这个扩展的 is 其实是 as 和 if 的组合。即它先进行 as 转换再进行 if 判断,判断其结果是否为 null,不等于 null 则执行语句块逻辑,反之不行。由上可知其实C# 7之前我们也可实现类似的功能,只是写法上比较繁琐。
2. switch语句更新(switch statement updates),如:
1 static int GetSum(IEnumerable<object> values)
2 {
3 var sum = 0;
4 if (values == null) return 0;
5
6 foreach (var item in values)
7 {
8 switch (item)
9 {
10 case 0: // 常量模式匹配
11 break;
12 case short sval: // 类型模式匹配
13 sum += sval;
14 break;
15 case int ival:
16 sum += ival;
17 break;
18 case string str when int.TryParse(str, out var result): // 类型模式匹配 + 条件表达式
19 sum += result;
20 break;
21 case IEnumerable<object> subList when subList.Any():
22 sum += GetSum(subList);
23 break;
24 default:
25 throw new InvalidOperationException("未知的类型");
26 }
27 }
28
29 return sum;
30 }
使用方法:
1 switch (item)
2 {
3 case type variable1:
4 // processing...
5 break;
6 case type variable2 when predicate:
7 // processing...
8 break;
9 default:
10 // processing...
11 break;
12 }
原理解析:此 switch 非彼 switch,编译后你会发现扩展的 switch 就是 as 、if 、goto 语句的组合体。同 is expressions 一样,以前我们也能实现只是写法比较繁琐并且可读性不强。
总结:模式匹配语法是想让我们在简单的情况下实现类似与多态一样的动态调用,即在运行时确定成员类型和调用具体的实现。
4. 局部引用和引用返回 (Ref locals and returns)
我们知道 C# 的 ref 和 out 关键字是对值传递的一个补充,是为了防止值类型大对象在Copy过程中损失更多的性能。现在在C# 7中 ref 关键字得到了加强,它不仅可以获取值类型的引用而且还可以获取某个变量(引用类型)的局部引用。如:
1 static ref int GetLocalRef(int[,] arr, Func<int, bool> func)
2 {
3 for (int i = 0; i < arr.GetLength(0); i++)
4 {
5 for (int j = 0; j < arr.GetLength(1); j++)
6 {
7 if (func(arr[i, j]))
8 {
9 return ref arr[i, j];
10 }
11 }
12 }
13
14 throw new InvalidOperationException("Not found");
15 }
Call:
1 int[,] arr = { { 10, 15 }, { 20, 25 } };
2 ref var num = ref GetLocalRef(arr, c => c == 20);
3 num = 600;
4
5 Console.WriteLine(arr[1, 0]);
Print results:
使用方法:
1. 方法的返回值必须是引用返回:
a) 声明方法签名时必须在返回类型前加上 ref 修饰。
b) 在每个 return 关键字后也要加上 ref 修饰,以表明是返回引用。
2. 分配引用(即赋值),必须在声明局部变量前加上 ref 修饰,以及在方法返回引用前加上 ref 修饰。
注:C# 开发的是托管代码,所以一般不希望程序员去操作指针。并由上述可知在使用过程中需要大量的使用 ref 来标明这是引用变量(编译后其实没那么多),当然这也是为了提高代码的可读性。
总结:虽然 C# 7 中提供了局部引用和引用返回,但为了防止滥用所以也有诸多约束,如:
1. 你不能将一个值分配给 ref 变量,如:
1 ref int num = 10; // error:无法使用值初始化按引用变量
2. 你不能返回一个生存期不超过方法作用域的变量引用,如:
1 public ref int GetLocalRef(int num) => ref num; // error: 无法按引用返回参数,因为它不是 ref 或 out 参数
3. ref 不能修饰 “属性” 和 “索引器”。
1 var list = new List<int>();
2 ref var n = ref list.Count; // error: 属性或索引器不能作为 out 或 ref 参数传递
原理解析:非常简单就是指针传递,并且个人觉得此语法的使用场景非常有限,都是用来处理大对象的,目的是减少GC提高性能。
5. 局部函数(Local functions)
C# 7 中的一个功能“局部函数”,如下所示:
1 static IEnumerable<char> GetCharList(string str)
2 {
3 if (IsNullOrWhiteSpace(str))
4 throw new ArgumentNullException(nameof(str));
5