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. 如何创建一个元组?

1   var tuple = (1, 2);                           // 使用语法糖创建元组
2 var tuple2 = ValueTuple.Create(1, 2); // 使用静态方法【Create】创建元组
3 var tuple3 = new ValueTuple<int, int>(1, 2); // 使用 new 运算符创建元组
4
5 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
6 return GetList();
7
8 IEnumerable<char> GetList()
9 {
10 for (int i = 0; i < str.Length; i++)
11 {
12 yield return str[i];
13 }
14 }
15 }

使用方法:

1   [数据类型,void] 方法名([参数])
2 {
3 // Method body;[] 里面都是可选项
4 }

原理解析:局部函数虽然是在其他函数内部声明,但它编译后就是一个被 internal 修饰的静态函数,它是属于类,至于它为什么能够使用上级函

数中的局部变量和参数呢?那是因为编译器会根据其使用的成员生成一个新类型(Class/Struct)然后将其传入函数中。由上可知则局部函数的声

明跟位置无关,并可无限嵌套。

总结:个人觉得局部函数是对 C# 异常机制在语义上的一次补充(如上例),以及为代码提供清晰的结构而设置的语法。但局部函数也有其缺点,

就是局部函数中的代码无法复用(反射除外)。

6. 更多的表达式体成员(More expression-bodied members)

C# 6 的时候就支持表达式体成员,但当时只支持“函数成员”和“只读属性”,这一特性在C# 7中得到了扩展,它能支持更多的成员:构造函数

、析构函数、带 get,set 访问器的属性、以及索引器。如下所示:

 1   public class Student
2 {
3 private string _name;
4
5 // Expression-bodied constructor
6 public Student(string name) => _name = name;
7
8 // Expression-bodied finalizer
9 ~Student() => Console.WriteLine("Finalized!");
10
11 // Expression-bodied get / set accessors.
12 public string Name
13 {
14 get => _name;
15 set => _name = value ?? "Mike";
16 }
17
18 // Expression-bodied indexers
19 public string this[string name] => Convert.ToBase64String(Encoding.UTF8.GetBytes(name));
20 }

备注:索引器其实在C# 6中就得到了支持,但其它三种在C# 6中未得到支持。

7. Throw 表达式(Throw expressions)

异常机制是C#的重要组成部分,但在以前并不是所有语句都可以抛出异常的,如:条件表达式(? :)、null合并运算符(??)、一些Lambda

表达式。而使用 C# 7 您可在任意地方抛出异常。如:

 1   public class Student
2 {
3 private string _name = GetName() ?? throw new ArgumentNullException(nameof(GetName));
4
5 private int _age;
6
7 public int Age
8 {
9 get => _age;
10 set => _age = value <= 0 || value >= 130 ? throw new ArgumentException("参数不合法") : value;
11 }
12
13 static string GetName() => null;
14 }

8. 扩展异步返回类型(Generalized async return types)

以前异步的返回类型必须是:Task、Task<T>、void,现在 C# 7 中新增了一种类型:ValueTask<T>,如下所示:

1   public async ValueTask<int> Func()
2 {
3 await Task.Delay(3000);
4 return 100;
5 }

总结:ValueTask<T> 与 ValueTuple 非常相似,所以就不列举: ValueTask<T> 与 Task 之间的异同了,但它们都是为了优化特定场景性能而

新增的类型。

  使用 ValueTask<T> 则需要导入: Install - Package System.Threading.Tasks.Extensions

9. 数字文本语法的改进(Numeric literal syntax improvements)

C# 7 还包含两个新特性:二进制文字、数字分隔符,如下所示:

1   var one = 0b0001;
2 var sixteen = 0b0001_0000;
3
4 long salary = 1000_000_000;
5 decimal pi = 3.141_592_653_589m;

注:二进制文本是以0b(零b)开头,字母不区分大小写;数字分隔符只有三个地方不能写:开头,结尾,小数点前后。

总结:二进制文本,数字分隔符 可使常量值更具可读性。

作者:雨夜潇湘 QQ群:
原文:30分钟掌握 C#7
出处:http://www.cnblogs.com/VVStudy/p/6551300.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

详解C#7.0新特性的更多相关文章

  1. Java lambda 表达式详解(JDK 8 新特性)

    什么是 lambda 表达式 lambda 表达式(拉姆达表达式)是 JAVA 8 中提供的一种新的特性,它使 Java 也能进行简单的"函数式编程". lambda 表达式的本质 ...

  2. iOS 9界面适配利器:详解Xcode 7的新特性UIStackView

    升级Xcode7后老项目storyBoard出现问题了,一看时新特性搞的鬼.具体 详见:http://www.csdn.net/article/2015-08-04/2825372

  3. [转]Servlet 3.0 新特性详解

    原文地址:http://blog.csdn.net/xiazdong/article/details/7208316 Servlet 3.0 新特性概览 1.Servlet.Filter.Listen ...

  4. Servlet 3.0 新特性详解

    转自:http://www.ibm.com/developerworks/cn/java/j-lo-servlet30/#major3 Servlet 是 Java EE 规范体系的重要组成部分,也是 ...

  5. 【转帖】Servlet 3.0 新特性详解

    http://www.ibm.com/developerworks/cn/java/j-lo-servlet30/ Servlet 3.0 新特性概述 Servlet 3.0 作为 Java EE 6 ...

  6. Servlet 3.0 新特性详解 (转载)

    原文地址:https://www.ibm.com/developerworks/cn/java/j-lo-servlet30/ Servlet 3.0 新特性概述 Servlet 3.0 作为 Jav ...

  7. HTTP详解(3)-http1.0 和http1.1 区别

    HTTP详解(3)-http1.0 和http1.1 区别 分类: 网络知识2013-03-17 16:51 1685人阅读 评论(0) 收藏 举报   目录(?)[+]   翻了下HTTP1.1的协 ...

  8. C#7.0&6.0新特性 — 完整版

    C#2.0 泛型 部分类型 匿名方法 迭代器 可空类型 Getter / setter单独可访问性 方法组转换(代表) Co- and Contra-variance for delegates 静态 ...

  9. webpack 4.0.0-beta.0 新特性介绍

    webpack 可以看做是模块打包机.它做的事情是:分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式 ...

随机推荐

  1. 建站相关-github+hexo, Markdown

    sunwhut的博客写的tutorial非常详细,参照该文一步步来会很顺利. 以后有时间也可以鼓捣一下Django. hexo: 使用了上面博客推荐的NexT主题.NexT主题配置方式见此文. hex ...

  2. AD账号解锁

    Get-ADUser -Filter * -Properties *  -SearchBase "dc=uxin,dc=youxinpai,dc=com"| ? {$_.locke ...

  3. python的学习之路day3

    大纲 1.汉字编码 2.浅拷贝与深拷贝 3.函数 4.return 5.函数的基本参数 6.format 7.三元运算(三目运算) 8.python的内置函数 abs() all() any() bo ...

  4. 深入浅出Windows命令——telnet

  5. PowerShell “execution of scripts is disabled on this system.”

    Set-ExecutionPolicy RemoteSigned

  6. MySql详解(三)

    MySql详解(三) 导入基础表 具体的SQL文件已经放入百度网盘,连接为:http://pan.baidu.com/s/1hseoVR2,后面的MySql内容都是按照这些基础表展开的. depart ...

  7. Invalid action class configuration that references an unknown class named [XX] .

    多次遇到这个错误,难以解决,有时候出现,有时候没有,很神奇,今天发现了一点端倪,虽然说不上找到了所有导致这个bug的原因.至少,也是很主要的一种了. 其实,透过结果,如果debug用心,一行代码一行代 ...

  8. [CTSC2018]混合果汁

    题目连接:https://www.luogu.org/problemnew/show/P4602 因为题中说是让最小值最大,所以自然想到二分答案.对于每一个二分的值,判断是否合法,若合法,在右区间二分 ...

  9. impala客户端连接

    想要使用plsql连接oracle一样,使用类似工具连接impala的方法:ClouderaImpalaODBC32.msi下载地址:http://www.cloudera.com/downloads ...

  10. Drool实战系列(二)之eclipse安装drools插件

    这里演示是drools7.5.0,大家可以根据自己需要安装不同的drools版本 drools安装地址: http://download.jboss.org/drools/release/ 一. 二. ...