1. out 变量(out variables)

以前我们使用out变量必须在使用前进行声明,C# 7.0 给我们提供了一种更简洁的语法 “使用时进行内联声明” 。如下所示:

var input = ReadLine();
if (int.TryParse(input, out var result))
{
WriteLine("您输入的数字是:{0}", input);
}
else
{
WriteLine("无法解析输入...");
}

上面代码编译后:

 int num;
string s = Console.ReadLine();
if (int.TryParse(s, out num))
{
Console.WriteLine("您输入的数字是:{0}", s);
}
else
{
Console.WriteLine("无法解析输入...");
}

原理解析:所谓的 “内联声明” 编译后就是以前的原始写法,只是现在由编译器来完成。

备注:在进行内联声明时,即可直接写明变量的类型也可以写隐式类型,因为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)开头,字母不区分大小写;数字分隔符只有三个地方不能写:开头,结尾,小数点前后。

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

快速掌握C#7的更多相关文章

  1. 快速构建H5单页面切换骨架

    在Web App和Hybrid App横行的时代,为了拥有更好的用户体验,单页面应用顺势而生,单页面应用简称`SPA`,即Single Page Application,就是只有一个HTML页面的应用 ...

  2. .net core快速上手

    2014年11月12日的Connect ();开发者活动上宣布将.NET堆栈基于MIT协议开源,并且提供开源保证,托管在Github上.当时的版本与最终目标相距甚远,然而有一点可以肯定的是,这是一个与 ...

  3. Web Api 入门实战 (快速入门+工具使用+不依赖IIS)

    平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html 屁话我也就不多说了,什么简介的也省了,直接简单概括+demo ...

  4. SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)

     SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...

  5. 快速搭建springmvc+spring data jpa工程

    一.前言 这里简单讲述一下如何快速使用springmvc和spring data jpa搭建后台开发工程,并提供了一个简单的demo作为参考. 二.创建maven工程 http://www.cnblo ...

  6. 如何快速优化手游性能问题?从UGUI优化说起

    WeTest 导读   本文作者从自身多年的Unity项目UI开发及优化的经验出发,从UGUI,CPU,GPU以及unity特有资源等几个维度,介绍了unity手游性能优化的一些方法.   在之前的文 ...

  7. Photoshop将普通照片快速制作二次元漫画风格效果

    今天为大家分享Photoshop将普通照片快速制作二次元漫画风格效果,教程很不错,对于喜欢漫画的朋友可以参考本文,希望能对大家有所帮助! 一提到日本动画电影,大家第一印象肯定是宫崎骏,但是日本除了宫崎 ...

  8. CRL快速开发框架系列教程十三(嵌套查询)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  9. CRL快速开发框架系列教程十二(MongoDB支持)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  10. CRL快速开发框架系列教程十一(大数据分库分表解决方案)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

随机推荐

  1. Appium 使用android_uiautomator定位元素时报错: The requested resource could not be found, or a request was received using an HTTP method that is not supported by the mapped resource

    使用 android_uiautomator 定位元素时(现在用的还不太熟,对于这个方法还需要加深了解)报错: 报错信息:The requested resource could not be fou ...

  2. BZOJ 1941: [Sdoi2010]Hide and Seek KDtree + 估价函数

    Code: #include<bits/stdc++.h> #define maxn 200000 #define inf 1000000000 using namespace std; ...

  3. 00_Rust安装及Hello World

    Rust 官网: https://www.rust-lang.org 版本:nightly.beta.stable 如何设计语言的讨论:https://github.com/rust-lang/rfc ...

  4. linux 头文件和库文件的设置

    GCC/G++会查找系统默认的include和link的路径,以及自己在编译命令中指定的路径.自己指定的路径就不说了,这里说明一下系统自动搜索的路径. [1]include头文件路径 除了默认的/us ...

  5. CODEVS 3500

    题目描述 输入3个数a,b,c,求a^b mod c=?输入描述          三个数a,b,c输出描述         一个数,即a^b mod c 的答案.样例输入5 10 9样例输出 4 基 ...

  6. Spring MVC 单元测试Demo

    @RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(locations={" ...

  7. js数组对象排序详解

    一.js对象遍历输出的时候真的是按照顺序输出吗? 下边就来实践一下: var obj={'3':'ccc',name:'abc',age:23,school:'sdfds',class:'dfd',h ...

  8. controller 接口写法

    import org.apache.commons.io.IOUtils; import javax.servlet.ServletInputStream; @RequestMapping(" ...

  9. 开源GIS软件 2

    Android上的导航软件 AndNav AndNav 是一款 Android 手机上的 GPS导航软件(非开源).软件支持GPS定位信息,目的地查询,道路建议管理,导航提示等功能,十分强大的一款软件 ...

  10. oracle 12c之前用sequence 和 trigger来生成自动增长的列

    SQL> create table scott.t1 (id number, val varchar2(8)); Table created. SQL> CREATE SEQUENCE s ...