[C#]6.0新特性浅谈
原文:[C#]6.0新特性浅谈
C#6.0出来也有很长一段时间了,虽然新的特性和语法趋于稳定,但是对于大多数程序猿来说,想在工作中用上C#6.0估计还得等上不短的一段时间。
所以现在再来聊一聊新版本带来的新特性可能也还不算晚吧?
一、nameof关键字
这绝对是整个新版本最让我期待的内容,它给代码重构带来了巨大的便利。
先来看一下它是怎么使用的吧:
string s;
Console.WriteLine(nameof(s));
s = nameof(s.Length);
Console.WriteLine(nameof(String));
Console.WriteLine(nameof(string.Length));
Console.WriteLine(nameof(string.Substring));
运行结果:
s
String
Length
SubString
通过上面的示例,可以看出来以下几点:
1.它不在乎变量是否已经初始化
2.它构成了一个运算结果为字符串的(编译时)表达式
3.它可以用于取得类型名,但是nameof(string)是不能通过编译的,小写的string是关键字而不是类型(这一点很值得吐槽。。。)
4.它的括号里面可以直接从类型取得实例属性
5.它可以取得方法名
然后看这段代码的IL:
IL_0000: ldstr "s"
IL_0005: call void [mscorlib]System.Console::WriteLine(string)
IL_000a: ldstr "String"
IL_000f: call void [mscorlib]System.Console::WriteLine(string)
IL_0014: ldstr "Length"
IL_0019: call void [mscorlib]System.Console::WriteLine(string)
IL_001e: ldstr "Substring"
IL_0023: call void [mscorlib]System.Console::WriteLine(string)
IL_0028: ret
编译后看不到nameof的痕迹,编译器把nameof的运算结果硬编码了,所以说它是一个"编译时运算符"。
适用场景:
1.空引用异常信息构成
2.ToString方法
3.IList数据绑定的列名
主要吐槽一下第三条吧,这是我最近工作里遇到的很闹心的一个事情,什么时候用上了6.0就能彻底解决这个麻烦了。。。
想象一下以前绑定一个自定义类型的List到ListBox吧,要设定DisplayMember和ValueMember的话就只能是硬编码,像是这样:
listBox1.DisplayMember = "ID";
listBox1.ValueMember = "Content";
一旦要对这个绑定类型的属性名称进行更改,工作量简直不敢想象。。。好一点的做法是用一套常量来代替硬编码,但是这样带来的麻烦是还得记着常量名。
不过以后用上了nameof就爽快了,一个Ctrl+R,R通通搞定~
二、[.?]空引用判断操作符
这算是一个用于简洁代码的语法糖吧,个人觉得实用价值一般般。
先看怎么用的吧:
string s = null;
s = s?.Substring();
// string expr_07 = this.s;
// this.s = ((expr_07 != null) ? expr_07.Substring(0) : null);
Console.WriteLine(s == null);
第二行代码与第三行被注释掉的部分,在编译过后是完全相等的。
同时也就是说一旦用了[.?],返回值就有可能是null,所以对于原本返回值类型的成员,只能赋值给Nullable<?>了,比如这样:
string s = null;
int? i = s?.IndexOf(".");
int j = s.IndexOf(".");
至于之后再要用到变量i,很多情况下仍然需要对是否空值进行判断。。。
同时这个语法糖也带来了歧义,比如这样:
object tag = form?.Tag;
由于Form和Tag都是引用类型,都可能为null,如果变量tag是null,这时候是没办法知道到底是form还是Tag返回了null(除非再判断一次。。。)。
三、字符串嵌入值
同样是一个用于简洁代码的语法糖,先看怎么用吧:
int i = ;
Console.WriteLine($"{nameof(i)} + 1 = {i + 1}");
Console.WriteLine($"{i + 1} * {i + 1} = 4");
运行结果:
i + = 2
2 * 2 = 4
然后是IL:
IL_0000: ldc.i4.1
IL_0001: stloc.0
IL_0002: ldstr "{0} + 1 = {1}"
IL_0007: ldstr "i"
IL_000c: ldloc.0
IL_000d: ldc.i4.1
IL_000e: add
IL_000f: box [mscorlib]System.Int32
IL_0014: call string [mscorlib]System.String::Format(string, object, object)
IL_0019: call void [mscorlib]System.Console::WriteLine(string)
IL_001e: ldstr "{0} * {1} = 4"
IL_0023: ldloc.0
IL_0024: ldc.i4.1
IL_0025: add
IL_0026: box [mscorlib]System.Int32
IL_002b: ldloc.0
IL_002c: ldc.i4.1
IL_002d: add
IL_002e: box [mscorlib]System.Int32
IL_0033: call string [mscorlib]System.String::Format(string, object, object)
IL_0038: call void [mscorlib]System.Console::WriteLine(string)
IL_003d: ret
可以看出来以下几点:
1.大括号可以用于包裹表达式
2.相同的表达式需要计算两次
介于第二条,对于资源消耗较多的运算,还是用一个中间变量放到$字符串中更好,要么直接使用String.Format。
同时需要注意的是,$和@同时使用的时候必须把$写在@之前,而在正则表达式中的大括号中的内容会被优先当做C#表达式计算一遍,比如:
Regex.IsMatch("AAA", $@"A{3}");
Regex.IsMatch("AAA", String.Format("A{0}", ))
上下两行的编译结果是一样的,然而这样的编译结果显然不是我们想要的,所以我建议在正则表达式上不要使用字符串嵌入值。
四、lambda方法体
仍然是用于简洁代码的特性,如下:
private void LambdaMethod() => Console.WriteLine(nameof(LambdaMethod));
private string LambdaProperty => nameof(LambdaProperty);
任何用一句话就能搞定的方法从此都可以扔掉大括号和return关键字了。注意第二行的内容,能且仅能实现属性的get方法,所以这构成了一个只读属性。
上面这两行内容其实就是相当于这样的:
private void LambdaMethod()
{
Console.WriteLine("LambdaMethod");
} private string LambdaProperty
{
get
{
return "LambdaProperty";
}
}
在以前的版本我也可能这么写:
private Action LambdaMethod = () => Console.WriteLine(nameof(LambdaMethod));
这种写法对于方法还好说,属性想要这么写就不行了。。。当然,这种写法总的来说是不可取的。
五、属性初始化器
这个特性算是盼星星盼月亮终于盼来了,虽然说重要性可能不是那么大,但是以前版本的C#居然不这么设计着实让我有些难以理解。。。
用法就像是在字段前加get set器,在属性后加赋值:
private string InitedProperty { get; set; } = "InitedProperty";
和上一条特性中的lambda属性看起来有点像,但是其实是有很大不同的:
1.带属性初始化器的属性就和自动set get器属性一样,是有自动生成的字段的;而lambda属性是不会自动生成私有字段的
2.属性初始化器的等号后只能是静态成员;而实例lambda属性中可以是任何表达式
3.属性初始化器等号后的表达式只会在类型加载时运算一次;而lambda属性的表达式会在每一次调用属性时即时运算
4.属性初始化器不影响属性可写性;而lambda属性就只能读了
基于以上第三条,如果初始化表达式耗费资源较多,应该使用属性初始化器而不是lambda属性。
六、索引初始化器
可以说这个语法糖是集合初始化器的升级版,让基于索引的集合初始化更加合理了。
现在初始化一个Dictionary可以这么写:
new Dictionary<int, string>
{
[] = "a",
[] = "e"
};
键值关系一目了然,而原来要初始化一个Dictionary得这么写:
new Dictionary<int, string>
{
{, "a"},
{, "b"}
};
光是一堆大括号就实在惹人吐槽。。。需要注意,集合初始化器与索引初始化器不能混合使用,当然我相信也没人会这么去做。。。
另外,下面这段代码也能够通过编译,不过运行时会出错:
new List<string>
{
[] = "a"
};
因为对于Dictionary,编译器知道该调用Add方法,而对于List,编译器只知道蠢蠢地对索引器进行赋值。。。
当然,不支持List的索引初始化一方面是因为集合初始化器的语法可以应付这种情况,另一方面也是因为可能出现这样的情况:
new List<string>
{
[] = "a",
[] = "c"
};
很显然List的Add方法没办法完成这项工作。。。
七、异常过滤器
这个算是新特性中较为重要也是改动很大的一个部分,先来看看怎么用的:
try
{
throw new IOException("Not Throw");
}
catch (IOException ex) when (ex.Message != "Need Throw")
{
Console.WriteLine(ex.Message);
}
catch (NullReferenceException ex)
{
Console.WriteLine(ex.Message);
throw;
}
运行结果:
Not Throw
这种过滤如果放在以前就得写得非常难看了:
try
{
throw new IOException("Not Throw");
}
catch (IOException ex)
{
if (ex.Message != "Need Throw")
{
Console.WriteLine(ex.Message);
}
else if (ex is NullReferenceException)
{
Console.WriteLine(ex2.Message);
throw;
}
else
{
throw
}
}
关键在于以前在catch块中捕获的异常没法传给下一个catch块了。
看一下新版代码的IL吧:
.try
{
IL_0000: ldstr "Not Throw"
IL_0005: newobj instance void [mscorlib]System.IO.IOException::.ctor(string)
IL_000a: throw
} // end .try
filter
{
IL_000b: isinst [mscorlib]System.IO.IOException
IL_0010: dup
IL_0011: brtrue.s IL_0017 IL_0013: pop
IL_0014: ldc.i4.0
IL_0015: br.s IL_002b IL_0017: stloc.0
IL_0018: ldloc.0
IL_0019: callvirt instance string [mscorlib]System.Exception::get_Message()
IL_001e: ldstr "Need Throw"
IL_0023: call bool [mscorlib]System.String::op_Inequality(string, string)
IL_0028: ldc.i4.0
IL_0029: cgt.un IL_002b: endfilter
} // end filter
catch
{
IL_002d: pop
IL_002e: ldloc.0
IL_002f: callvirt instance string [mscorlib]System.Exception::get_Message()
IL_0034: call void [mscorlib]System.Console::WriteLine(string)
IL_0039: leave.s IL_0047
} // end handler
catch [mscorlib]System.NullReferenceException
{
IL_003b: callvirt instance string [mscorlib]System.Exception::get_Message()
IL_0040: call void [mscorlib]System.Console::WriteLine(string)
IL_0045: rethrow
} // end handler
好像看到了什么不得了的东西,居然出现了一个filter块。看来第一段代码try块构造的异常完全没有进catch块,这一点与以前的处理完全不一样了。
同时注意到在filter块下面还有一个未标明异常类型的catch块,从内容来看就是对应到C#代码的when后第一个大括号。
filter块中大概是这么个流程:
1.检验异常类型,true时走下一步,false时进入空引用异常的catch块
2.对when中表达式进行计算
3.endfilter判断上一步的结果,true时进入对应的catch块,false时进入空引用异常的catch块
可以看到,when的作用就是在catch块前插入一个filter块,而endfilter指令做的事情就是依据堆栈顶的值选择进入这个catch块还是将控制转移到异常处理程序。
八、静态成员引用
这个特性很久以前就在Java中出现了,而C#6.0也终于将其引入。
其实早在引入扩展方法的时候就已经破坏了定义类型可知性,然而扩展方法带来的好处实在太大了。
使用方法如下:
using static System.String;
...
Console.WriteLine(Concat("a", "b"));
注意到Concat方法是来自于String类型,也就是说静态引用针对的是成员而不是类型,using static后面不一定是静态类型。
这个特效带来的好处当然就是方便省事咯,坏处也很明显,就是比扩展方法有过之而无不及的对定义类型可知性的破坏,所以在使用这个特性的时候还是需要非常谨慎。
适用的成员必须是所有人都很清楚来由的,比如WriteLine、Format,一看就能知道方法是在Console和String类型中定义,而不是当前类型。
九、catch、finally中的await
终于可以在异常处理中愉快地使用异步编程语法糖了:
private async void Test()
{
try
{
await new Task<int>(() =>
{
return ;
});
}
catch
{
await new Task<int>(() =>
{
return ;
});
}
finally
{
await new Task<int>(() =>
{
return ;
});
}
}
最后祝愿Win10能赶紧普及起来,这样广大的.Net程序员才能真正用上这些神兵利器。
[C#]6.0新特性浅谈的更多相关文章
- JDK各版本新特性浅谈
JDK 5.0 自动拆装箱 枚举 可变参数 泛型 For -each 内省 静态导入 JDK 6.0 console开发控制台程序 轻量级HTTP ServerAPI 支持脚本语言 使用Compile ...
- 浅谈Tuple之C#4.0新特性那些事儿你还记得多少?
来源:微信公众号CodeL 今天给大家分享的内容基于前几天收到的一条留言信息,留言内容是这样的: 看了这位网友的留言相信有不少刚接触开发的童鞋们也会有同样的困惑,除了用新建类作为桥梁之外还有什么好的办 ...
- [翻译] C# 8.0 新特性 Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南) 【由浅至深】redis 实现发布订阅的几种方式 .NET Core开发者的福音之玩转Redis的又一傻瓜式神器推荐
[翻译] C# 8.0 新特性 2018-11-13 17:04 by Rwing, 1179 阅读, 24 评论, 收藏, 编辑 原文: Building C# 8.0[译注:原文主标题如此,但内容 ...
- Vue3.0新特性
Vue3.0新特性 Vue3.0的设计目标可以概括为体积更小.速度更快.加强TypeScript支持.加强API设计一致性.提高自身可维护性.开放更多底层功能. 描述 从Vue2到Vue3在一些比较重 ...
- Java基础和JDK5.0新特性
Java基础 JDK5.0新特性 PS: JDK:Java Development KitsJRE: Java Runtime EvironmentJRE = JVM + ClassLibary JV ...
- Visual Studio 2015速递(1)——C#6.0新特性怎么用
系列文章 Visual Studio 2015速递(1)——C#6.0新特性怎么用 Visual Studio 2015速递(2)——提升效率和质量(VS2015核心竞争力) Visual Studi ...
- atitit.Servlet2.5 Servlet 3.0 新特性 jsp2.0 jsp2.1 jsp2.2新特性
atitit.Servlet2.5 Servlet 3.0 新特性 jsp2.0 jsp2.1 jsp2.2新特性 1.1. Servlet和JSP规范版本对应关系:1 1.2. Servlet2 ...
- 背水一战 Windows 10 (1) - C# 6.0 新特性
[源码下载] 背水一战 Windows 10 (1) - C# 6.0 新特性 作者:webabcd 介绍背水一战 Windows 10 之 C# 6.0 新特性 介绍 C# 6.0 的新特性 示例1 ...
- C# 7.0 新特性2: 本地方法
本文参考Roslyn项目中的Issue:#259. 1. C# 7.0 新特性1: 基于Tuple的“多”返回值方法 2. C# 7.0 新特性2: 本地方法 3. C# 7.0 新特性3: 模式匹配 ...
随机推荐
- Naive Bayes(朴素贝叶斯算法)[分类算法]
Naïve Bayes(朴素贝叶斯)分类算法的实现 (1) 简介: (2) 算法描述: (3) <?php /* *Naive Bayes朴素贝叶斯算法(分类算法的实现) */ /* *把. ...
- 常量 - PHP手册笔记
常量语法 常量在脚本执行期间其值不能改变.常量大小写敏感,传统上常量标识符总是大写.常量一旦定义就不能被重新定义或取消定义,常量的值只能是标量. 可以用define()函数来定义常量,也可以使用con ...
- 变量 - PHP手册笔记
基础 PHP中的变量用一个美元符号后面跟变量名来表示.变量名是区分大小写的,并且出现中文可能也是合法的. 变量默认总是传值赋值.PHP也提供了另外一种方式给变量赋值:引用赋值.这意味着新的变量简单的引 ...
- python运维开发(十一)----python操作缓存memcache、redis
内容目录: 缓存 memcache redis memcache Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数 ...
- The package does not support the device architecture (x86). You can change the supported architectures in the Android Build section of the Project Opt
The package does not support the device architecture (x86). You can change the supported architectur ...
- Nginx 配置指令的执行顺序(三)
如前文所述,除非像 ngx_set_misc 模块那样使用特殊技术,其他模块的配置指令即使是在 rewrite 阶段运行,也不能和 ngx_rewrite 模块的指令混合使用.不妨来看几个这样的例子. ...
- 什么是Intent(意图)
1.Intent是一种运行时绑定(runtime bingding)机制,它能在程序运行的过程中连接两个不同的组件.通过Intent,你的程序可以向Android表达某种 请求或者意愿,Android ...
- 20个Linux命令及Linux终端的趣事
20个Linux命令及Linux终端的趣事 . 命令:sl (蒸汽机车) 你可能了解 ‘ls’ 命令,并经常使用它来查看文件夹的内容.但是,有些时候你可能会拼写成 ‘sl’ ,这时我们应该如何获得一些 ...
- 【JSP实例】指定用户计数器
不同的用户访问次数是不一样的,因此对于每一个用户的访问次数都要进行统计,以适应需要. 用户登陆的Login.html的源文件: <html> <head> <title& ...
- linux大事件集
1,RHEL 6.6 Beta为RHEL 6.x用户提供了对远程直接内存访问(RDMA)聚合以太网(RoCE)的支持(IB卡,Mellanox),带来低延迟.高带宽的网络连接; 2,rhel7 201 ...