[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));

运行结果:

sStringLength
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(1);
// 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 = 1;
Console.WriteLine($"{nameof(i)} + 1 = {i + 1}");
Console.WriteLine($"{i + 1} * {i + 1} = 4");

运行结果:

i + 1 = 22 * 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}", 3))

上下两行的编译结果是一样的,然而这样的编译结果显然不是我们想要的,所以我建议在正则表达式上不要使用字符串嵌入值。

四、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>
{
    [1] = "a",
    [5] = "e"
};

键值关系一目了然,而原来要初始化一个Dictionary得这么写:

new Dictionary<int, string>
{
    {1, "a"},
    {5, "b"}
};

光是一堆大括号就实在惹人吐槽。。。需要注意,集合初始化器与索引初始化器不能混合使用,当然我相信也没人会这么去做。。。
另外,下面这段代码也能够通过编译,不过运行时会出错:

new List<string>
{
    [0] = "a"
};

因为对于Dictionary,编译器知道该调用Add方法,而对于List,编译器只知道蠢蠢地对索引器进行赋值。。。
当然,不支持List的索引初始化一方面是因为集合初始化器的语法可以应付这种情况,另一方面也是因为可能出现这样的情况:

new List<string>
{
    [0] = "a",
    [2] = "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 1;
        });
    }
    catch
    {
        await new Task<int>(() =>
        {
            return 1;
        });
    }
    finally
    {
        await new Task<int>(() =>
        {
            return 1;
        });
    }
}

最后祝愿Win10能赶紧普及起来,这样广大的.Net程序员才能真正用上这些神兵利器。

时间: 2024-10-01 20:31:42

[C#]6.0新特性浅谈的相关文章

MySQL 5.0 新特性--存储过程

Introduction 简介 MySQL 5.0 新特性教程是为需要了解5.0版本新特性的MySQL老用户而写的.简单的来说是介绍了"存储过程.触发器.视图.信息架构视图",在此感谢译者陈朋奕的努力. 希望这本书能像内行专家那样与您进行对话,用简单的问题.例子让你学到需要的知识.为了达到这样的目的,我会从每一个细节开始慢慢的为大家建立概念,最后会给大家展示较大的实用例,在学习之前也许大家会认为这个用例很难,但是只要跟着课程去学,相信很快就能掌握. Conventions and St

MySQL5.0新特性教程 存储过程:第三讲

The New SQL Statements 新SQL语句 Variables 变量 在复合语句中声明变量的指令是DECLARE. (1) Example with two DECLARE statements 两个DECLARE语句的例子 WHILE ... END WHILE CREATE PROCEDURE p8 () BEGIN DECLARE a INT; DECLARE b INT; SET a = 5; SET b = 5; INSERT INTO t VALUES (a); SE

PHP 5昨天隆重推出--PHP 5/Zend Engine 2.0新特性

前言    今天突然想到PHP官方网站上一转,一眼就看到PHP5推出的通告.虽然以前看到过PHP5的预告,但还是仔细看了PHP 5/Zend Engine 2.0新特性一文,一股JAVA气息扑面而来...   特将该文试译出来,首发于CSDN网站,以飨读者. PHP 5/Zend Engine 2.0新特性徐唤春 译 sfwebsite@hotmail.comhttp://www.php.net/zend-engine-2.php 全新的对象模型PHP中的对象处理部分已完全重写,具有更佳的性能和

MySQL 5.0新特性教程 存储过程:第一讲

mysql|存储过程|教程 作者:mysql AB;翻译:陈朋奕 Introduction 简介 MySQL 5.0 新特性教程是为需要了解5.0版本新特性的MySQL老用户而写的.简单的来说是介绍了"存储过程.触发器.视图.信息架构视图",在此感谢译者陈朋奕的努力. 希望这本书能像内行专家那样与您进行对话,用简单的问题.例子让你学到需要的知识.为了达到这样的目的,我会从每一个细节开始慢慢的为大家建立概念,最后会给大家展示较大的实用例,在学习之前也许大家会认为这个用例很难,但是只要跟着

ASP 3.0新特性概要

 ASP 3.0新特性概要 在ASP3.0中,有一些新的特性或经历较大的变化或改进的特性.1. 无脚本的ASPASP处理不包括任何脚本的.asp页的速度是很快的,假如你正在创建的站点或Web应用程序文件最终可能使用ASP,最好让这些文件使用.asp文件扩展名,而不用考虑它们是包含服务器端脚本还是仅仅包含静态(HTML和文本)内容. 2. 新的流向控制能力到目前为止,假如想把执行转向另外的一个ASP页,不得不使用Response.Redirect语句,这个工作通过向客户端发送一个响应来指示其载入新

MySQL 5.0新特性教程 存储过程:第一讲

Introduction 简介 MySQL 5.0 新特性教程是为需要了解5.0版本新特性的MySQL老用户而写的.简单的来说是介绍了"存储过程.触发器.视图.信息架构视图",在此感谢译者陈朋奕的努力. 希望这本书能像内行专家那样与您进行对话,用简单的问题.例子让你学到需要的知识.为了达到这样的目的,我会从每一个细节开始慢慢的为大家建立概念,最后会给大家展示较大的实用例,在学习之前也许大家会认为这个用例很难,但是只要跟着课程去学,相信很快就能掌握. Conventions and St

Servlet 3.0新特性详解

简介:Servlet 是 Java EE 规范体系的重要组成部分,也是 Java 开发人员必须具备的基础技能, Servlet 3.0 是 Servlet 规范的最新版本.本文主要介绍了 Servlet 3.0 引入的若干重要新特性,包括 异步处理.新增的注解支持.可插性支持等等,为读者顺利向新版本过渡扫清障碍. Servlet 3.0 新特性概述 Servlet 3.0 作为 Java EE 6 规范体系中一员,随着 Java EE 6 规范一起发布.该版本在前一版本 (Servlet 2.5

c# 6.0新特性(一)

写在前面 接近年底了,基本上没什么活了,就学点新东西,就想着了解下c# 6.0的新特性.在code project上看到了一篇不错的文章,就准备翻译一下,顺便照着学习学习.废话不多说,直奔主题. 原文地址:http://www.codeproject.com/Articles/1070659/All-About-Csharp-New-Features 简介 众所周知,c# 6.0 是在visual studio 2015中引入的.在其他的几个版本中同样引入一些特性,比如在c# 3.0中引入了li

WebSphere Application Server V7.0新特性及各Java EE标准版本支持之对比

WebSphere Application Server V7.0新特性及各Java EE标准版本支持之对比 Application Server Network Deployment, Version 7.0 Operating Systems: AIX, HP-UX, i5/OS, Linux, Solaris, Windows, z/OS Specifications and API documentation 对比的WebSphere版本如下: Version 7.0 Version 6