30分钟掌握 C#7
1. 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关键字修饰的一定是局部变量。
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 = (, ); // 使用语法糖创建元组
var tuple2 = ValueTuple.Create(, ); // 使用静态方法【Create】创建元组
var tuple3 = new ValueTuple<int, int>(, ); // 使用 new 运算符创建元组 WriteLine($"first:{tuple.Item1}, second:{tuple.Item2}, 上面三种方式都是等价的。");
原理解析:上面三种方式最终都是使用 new 运算符来创建实例。
2. 如何创建给字段命名的元组?
// 左边指定字段名称
(int one, int two) tuple = (, );
WriteLine($"first:{tuple.one}, second:{tuple.two}"); // 右边指定字段名称
var tuple2 = (one: , two: );
WriteLine($"first:{tuple2.one}, second:{tuple2.two}"); // 左右两边同时指定字段名称
(int one, int two) tuple3 = (first: , second: ); /* 此处会有警告:由于目标类型(xx)已指定了其它名称,因为忽略元组名称xxx */
WriteLine($"first:{tuple3.one}, second:{tuple3.two}");
注:左右两边同时指定字段名称,会使用左边的字段名称覆盖右边的字段名称(一一对应)。
原理解析:上述给字段命名的元组在编译后其字段名称还是:Item1, Item2...,即:“命名”只是语义上的命名。
3. 什么是解构?
解构顾名思义就是将整体分解成部分。
4. 解构元组,如下所示:
var (one, two) = GetTuple(); WriteLine($"first:{one}, second:{two}");
static (int, int) GetTuple()
{
return (, );
}
原理解析:解构元组就是将元组中的字段值赋值给声明的局部变量(编译后可查看)。
备注:在解构时“=”左边能提取变量的数据类型(如上所示),元组中字段类型相同时即可提取具体类型也可以是隐式类型,但元组中字段类型
不相同时只能提取隐式类型。
5. 解构可以应用于 .Net 的任意类型,但需要编写 Deconstruct 方法成员(实例或扩展)。如下所示:
public class Student
{
public Student(string name, int age)
{
Name = name;
Age = age;
} public string Name { get; set; } public int Age { get; set; } public void Deconstruct(out string name, out int age)
{
name = Name;
age = Age;
}
}
使用方式如下:
var (Name, Age) = new Student("Mike", ); WriteLine($"name:{Name}, age:{Age}");
原理解析:编译后就是由其实例调用 Deconstruct 方法,然后给局部变量赋值。
Deconstruct 方法签名:
// 实例签名
public void Deconstruct(out type variable1, out type variable2...) // 扩展签名
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),如:
static int GetSum(IEnumerable<object> values)
{
var sum = ;
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 = ;
if (values == null) return ; foreach (var item in values)
{
switch (item)
{
case : // 常量模式匹配
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 一样,以前我们也能实
现只是写法比较繁琐并且可读性不强。
总结:模式匹配语法是想让我们在简单的情况下实现类似与多态一样的动态调用,即在运行时确定成员类型和调用具体的实现。
4. 局部引用和引用返回 (Ref locals and returns)
我们知道 C# 的 ref 和 out 关键字是对值传递的一个补充,是为了防止值类型大对象在Copy过程中损失更多的性能。现在在C# 7中 ref 关键字得
到了加强,它不仅可以获取值类型的引用而且还可以获取某个变量(引用类型)的局部引用。如:
static ref int GetLocalRef(int[,] arr, Func<int, bool> func)
{
for (int i = ; i < arr.GetLength(); i++)
{
for (int j = ; j < arr.GetLength(); j++)
{
if (func(arr[i, j]))
{
return ref arr[i, j];
}
}
} throw new InvalidOperationException("Not found");
}
Call:
int[,] arr = { { , }, { , } };
ref var num = ref GetLocalRef(arr, c => c == );
num = ; Console.WriteLine(arr[, ]);
Print results:
使用方法:
1. 方法的返回值必须是引用返回:
a) 声明方法签名时必须在返回类型前加上 ref 修饰。
b) 在每个 return 关键字后也要加上 ref 修饰,以表明是返回引用。
2. 分配引用(即赋值),必须在声明局部变量前加上 ref 修饰,以及在方法返回引用前加上 ref 修饰。
注:C# 开发的是托管代码,所以一般不希望程序员去操作指针。并由上述可知在使用过程中需要大量的使用 ref 来标明这是引用变量(编译后其
实没那么多),当然这也是为了提高代码的可读性。
总结:虽然 C# 7 中提供了局部引用和引用返回,但为了防止滥用所以也有诸多约束,如:
1. 你不能将一个值分配给 ref 变量,如:
ref int num = ; // 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提高性能。
5. 局部函数(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 = ; i < str.Length; i++)
{
yield return str[i];
}
}
}
使用方法:
[数据类型,void] 方法名([参数])
{
// Method body;[] 里面都是可选项
}
原理解析:局部函数虽然是在其他函数内部声明,但它编译后就是一个被 internal 修饰的静态函数,它是属于类,至于它为什么能够使用上级函
数中的局部变量和参数呢?那是因为编译器会根据其使用的成员生成一个新类型(Class/Struct)然后将其传入函数中。由上可知则局部函数的声
明跟位置无关,并可无限嵌套。
总结:个人觉得局部函数是对 C# 异常机制在语义上的一次补充(如上例),以及为代码提供清晰的结构而设置的语法。但局部函数也有其缺点,
就是局部函数中的代码无法复用(反射除外)。
6. 更多的表达式体成员(More expression-bodied members)
C# 6 的时候就支持表达式体成员,但当时只支持“函数成员”和“只读属性”,这一特性在C# 7中得到了扩展,它能支持更多的成员:构造函数
、析构函数、带 get,set 访问器的属性、以及索引器。如下所示:
public class Student
{
private string _name; // Expression-bodied constructor
public Student(string name) => _name = name; // Expression-bodied finalizer
~Student() => Console.WriteLine("Finalized!"); // Expression-bodied get / set accessors.
public string Name
{
get => _name;
set => _name = value ?? "Mike";
} // Expression-bodied indexers
public string this[string name] => Convert.ToBase64String(Encoding.UTF8.GetBytes(name));
}
备注:索引器其实在C# 6中就得到了支持,但其它三种在C# 6中未得到支持。
7. 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 <= || value >= ? throw new ArgumentException("参数不合法") : value;
} static string GetName() => null;
}
8. 扩展异步返回类型(Generalized async return types)
以前异步的返回类型必须是:Task、Task<T>、void,现在 C# 7 中新增了一种类型:ValueTask<T>,如下所示:
public async ValueTask<int> Func()
{
await Task.Delay();
return ;
}
总结:ValueTask<T> 与 ValueTuple 非常相似,所以就不列举: ValueTask<T> 与 Task 之间的异同了,但它们都是为了优化特定场景性能而
新增的类型。
使用 ValueTask<T> 则需要导入: Install - Package System.Threading.Tasks.Extensions
9. 数字文本语法的改进(Numeric literal syntax improvements)
C# 7 还包含两个新特性:二进制文字、数字分隔符,如下所示:
var one = 0b0001;
var sixteen = 0b0001_0000; long salary = 1000_000_000;
decimal pi = .141_592_653_589m;
注:二进制文本是以0b(零b)开头,字母不区分大小写;数字分隔符只有三个地方不能写:开头,结尾,小数点前后。
总结:二进制文本,数字分隔符 可使常量值更具可读性。
30分钟掌握 C#7的更多相关文章
- 30分钟学会XAML
1.狂妄的WPF 相对传统的Windows图形编程,需要做很多复杂的工作,引用许多不同的API.例如:WinForm(带控件表单).GDI+(2D图形).DirectX API(3D图形)以及流媒体和 ...
- Shell脚本编程30分钟入门
Shell脚本编程30分钟入门 转载地址: Shell脚本编程30分钟入门 什么是Shell脚本 示例 看个例子吧: #!/bin/sh cd ~ mkdir shell_tut cd shell_t ...
- JS组件系列——又一款MVVM组件:Vue(一:30分钟搞定前端增删改查)
前言:关于Vue框架,好几个月之前就听说过,了解一项新技术之后,总是处于观望状态,一直在犹豫要不要系统学习下.正好最近有点空,就去官网了解了下,看上去还不错的一个组件,就抽空研究了下.最近园子里vue ...
- 2016windows(10) wamp 最简单30分钟thrift入门使用讲解,实现php作为服务器和客户端的hello world
2016最简单windows(10) wamp 30分钟thrift入门使用讲解,实现php作为服务器和客户端的hello world thrift是什么 最简单解释 thrift是用来帮助各个编程语 ...
- 30分钟全面解析-SQL事务+隔离级别+阻塞+死锁
以前总是追求新东西,发现基础才是最重要的,今年主要的目标是精通SQL查询和SQL性能优化. 本系列主要是针对T-SQL的总结. [T-SQL基础]01.单表查询-几道sql查询题 [T-SQL基础] ...
- 30分钟?不需要,轻松读懂IL
先说说学IL有什么用,有人可能觉得这玩意平常写代码又用不上,学了有个卵用.到底有没有卵用呢,暂且也不说什么学了可以看看一些语法糖的实现,或对.net理解更深一点这些虚头巴脑的东西.最重要的理由就是一个 ...
- 【grunt第二弹】30分钟学会使用grunt打包前端代码(02)
前言 上一篇博客,我们简单的介绍了grunt的使用,一些基础点没能覆盖,我们今天有必要看看一些基础知识 [grunt第一弹]30分钟学会使用grunt打包前端代码 配置任务/grunt.initCon ...
- Objective-C 30分钟入门教程
Objective-C 30分钟入门教程 我第一次看OC觉得这个语言的语法有些怪异,为什么充满了@符号,[]符号,函数调用没有()这个,但是面向对象的高级语言也不外乎类,接口,多态,封装,继承等概念. ...
- AngularJS 30分钟快速入门【译】
引用自:http://www.revillweb.com/tutorials/angularjs-in-30-minutes-angularjs-tutorial/,翻译如下: 简介 我三年前开始使用 ...
- 30分钟学会如何使用Shiro
本篇内容大多总结自张开涛的<跟我学Shiro>原文地址:http://jinnianshilongnian.iteye.com/blog/2018936 我并没有全部看完,只是选择了一部分 ...
随机推荐
- web前端学习路线推荐(讲的很细致)
前端要学习三个部分:HTML,CSS,JavaScript(简称JS),因此首先明确三个概念:HTML是内容层,它的目的是表示一个HTML标签在页面里是个什么角色. CSS是样式层,它的目的是表示一块 ...
- 读书笔记 effective c++ Item 22 将数据成员声明成private
我们首先看一下为什么数据成员不应该是public的,然后我们将会看到应用在public数据成员上的论证同样适用于protected成员.最后够得出结论:数据成员应该是private的. 1. 为什么数 ...
- 基于vue2.0+vuex+localStorage开发的本地记事本
本文采用vue2.0+vuex+localStorage+sass+webpack,实现一个本地存储的记事本.兼容PC端和移动端.在线预览地址:DEMO github地址:https://github ...
- hibernate动态切换数据源
起因: 公司的当前产品,主要是两个项目集成的,一个是java项目,还有一个是php项目,两个项目用的是不同的数据源,但都是mysql数据库,因为java这边的开发工作已经基本完成了,而php那边任务还 ...
- Handlebars模板引擎之进阶
取得索引 我想取得索引作为序号这个是常用的.在handlebars也是存在的. 就是使用 @index 来获取索引 {{#each this}} <tr> <td>{{ @in ...
- Python入门教程(3)
人生苦短,我学Pyhton Python(英语发音:/ˈpaɪθən/), 是一种面向对象.解释型计算机程序设计语言,由Guido van Rossum于1989年底发明,第一个公开发行版发行于199 ...
- nodejs中的异步流程序控制nsync
异步编程是指由于异步I/O等因素,无法同步获得执行结果时, 在回调函数中进行下一步操作的代码编写风格,常见的如setTimeout函数.ajax请求等等 http://cnodejs.org/topi ...
- 《你不知道的JavaScript》整理(六)——强制类型转换
JavaScript中通常分为两种类型转换,"隐式强制类型转换"(implicit coercion)和"显式强制类型转换"(explicit coercion ...
- HTM5新手学习的一些日常总结,相互交流和相互学习。
第一天 一.HTML--网页的源码(超文本标签语言) HTML标签 标签式是HTML最基本单位和最重要的组成. 使<和>扩起来 标签都是闭合的(规范) HTML标签属性 是标签的一部分,用 ...
- SEO-站外优化规范
站外优化规范 新站 前期(提高网站曝光率<信息发布平台>) 一. 分类目录信息发布 二. 黄页网信息发布 三. 友链平台信息发布 四. 各大论坛引蜘蛛区信息发布 五. 网址提交 六. 社区 ...