0. 目录

C#6 新增特性目录

1. 老版本的代码

 namespace csharp6
{
internal class Person
{
public string Name { get; set; }
} internal class Program
{
private static void Main()
{
Person person = null;
//if判断
string name = null;
if (person != null)
{
name = person.Name;
}
}
}
}

在我们使用一个对象的属性的时候,有时候第一步需要做的事情是先判断这个对象本身是不是bull,不然的话你可能会得到一个 System.NullReferenceException 的异常。虽然有时候我们可以使用三元运算符 string name = person != null ? person.Name : null; 来简化代码,但是这种书写方式还是不够简单......由于null值检测时编程中非常常用的一种编码行为,so,C#6为我们带来了一种更为简化的方式。

2. null条件运算符

 namespace csharp6
{
internal class Person
{
public string Name { get; set; }
} internal class Program
{
private static void Main()
{
Person person = null;
string name = person?.Name;
}
}
}

从上面我们可以看出,使用 ?. 这种方式可以代替if判断和简化三元运算符的使用,简洁到不能再简洁了吧。按照惯例,上两份IL代码对比对比。

老版本的IL代码:

 .method private hidebysig static void  Main() cil managed
{
.entrypoint
// Code size 23 (0x17)
.maxstack
.locals init ([] class csharp6.Person person,
[] string name,
[] bool V_2)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldnull
IL_0004: stloc.1
IL_0005: ldloc.0
IL_0006: ldnull
IL_0007: cgt.un
IL_0009: stloc.2
IL_000a: ldloc.2
IL_000b: brfalse.s IL_0016
IL_000d: nop
IL_000e: ldloc.0
IL_000f: callvirt instance string csharp6.Person::get_Name()
IL_0014: stloc.1
IL_0015: nop
IL_0016: ret
} // end of method Program::Main

if版的IL

新语法的IL:

 .method private hidebysig static void  Main() cil managed
{
.entrypoint
// Code size 17 (0x11)
.maxstack
.locals init ([] class csharp6.Person person,
[] string name)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: brtrue.s IL_0009
IL_0006: ldnull
IL_0007: br.s IL_000f
IL_0009: ldloc.0
IL_000a: call instance string csharp6.Person::get_Name()
IL_000f: stloc.1
IL_0010: ret
} // end of method Program::Main

null条件运算符版的IL

咦,貌似有很大不一样,我们再来一份三元运算符版的IL看看:

 .method private hidebysig static void  Main() cil managed
{
.entrypoint
// Code size 17 (0x11)
.maxstack
.locals init ([] class csharp6.Person person,
[] string name)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: brtrue.s IL_0009
IL_0006: ldnull
IL_0007: br.s IL_000f
IL_0009: ldloc.0
IL_000a: callvirt instance string csharp6.Person::get_Name()
IL_000f: stloc.1
IL_0010: ret
} // end of method Program::Main

三元运算符版的IL

新语法"?."和三元运算符"?:"的结果是唯一的差别是IL_000a这一行。"?."的方式被编译为call,而"?:"的方式被编译为callvirt,不知为何"?:"中的persion.Name为何会被编译成支持多态方式调用的callvirt,在这种情况下貌似call效率会更高一些,但是终究"?."和"?:"编译的代码没有本质差异。

但是和if判断的相比简化了一些,我们分析下IL,看看有哪些差异(这里就忽略call和callvirt的区别了):

if版的IL分析:

 .method private hidebysig static void  Main() cil managed
{
.entrypoint
.maxstack
.locals init ([] class csharp6.Person person, //初始化局部变量person,把person放在索引为0的位置
[] string name, //初始化局部变量name,把name放在索引为1的位置
[] bool V_2) //初始化局部变量V_2,把V_2放在索引为2的位置
IL_0000: nop //空
IL_0001: ldnull //加载null
IL_0002: stloc.0 //把null放入索引为0的变量,也就是person对象。
IL_0003: ldnull //加载null
IL_0004: stloc.1 //把null放入索引为1的变量,也就是name对象。
IL_0005: ldloc.0 //加载索引为0的位置的变量,也就是person对象
IL_0006: ldnull //加载null
IL_0007: cgt.un //比较前两步加载的值。如果第一个值大于第二个值,则将整数值1推送到计算堆栈上;反之,将0推送到计算堆栈上。
IL_0009: stloc.2 //把比较结果放入索引为2的变量中,也就是V_2对象
IL_000a: ldloc.2 //加载索引为2的对象,也就是V_2对象
IL_000b: brfalse.s IL_0016 //如果上一步加载的对象为false、空引用或零,则跳转到IL_0016位置,也就是结束当前方法。
IL_000d: nop //空
IL_000e: ldloc.0 //加载索引为0的位置的变量,也就是person对象
IL_000f: callvirt instance string csharp6.Person::get_Name() //调用person对象的get_Name方法。
IL_0014: stloc.1 //把上一步的结果存入索引为1的变量中,也就是name对象。
IL_0015: nop //空
IL_0016: ret //返回
}

null条件运算符版的IL分析:

 .method private hidebysig static void  Main() cil managed
{
.entrypoint
.maxstack
.locals init ([] class csharp6.Person person, //初始化局部变量person,把person放在索引为0的位置
[] string name) //初始化局部变量name,把name放在索引为1的位置
IL_0000: nop //空
IL_0001: ldnull //加载null
IL_0002: stloc.0 //把null放入索引为0的变量,也就是person对象
IL_0003: ldloc.0 //加载索引为0的位置的变量,也就是person对象
IL_0004: brtrue.s IL_0009 //如果上一步加载的对象为true、非空引用或非零,则跳转到IL_0009位置
IL_0006: ldnull //加载null
IL_0007: br.s IL_000f //无条件的跳转到IL_000f处
IL_0009: ldloc.0 //加载索引为0的位置的变量,也就是person对象
IL_000a: call instance string csharp6.Person::get_Name() ////调用person对象的get_Name方法。
IL_000f: stloc.1 //把上一步的结果存入索引为1的变量中,也就是name对象。
IL_0010: ret //返回
}

通过分析我们发现,null运算符编译后的IL代码更简短,使用了2个分支跳转,简化了判断逻辑,而if版的IL还多出来一个bool类型的V_2临时变量。

so,结论就是"?."的和三元运算符"?:"的编译结果是一样的,而且简化了if的判断。所以不管是从性能还是可读性方面考虑,"?."都是推荐的写法。

3. Example

3.1 ?[

null条件运算符不但可以使用 ?. 的语法访问对象的属性和方法,还可以用 ?[ 的语法访问检测数组或包含索引器的对象是否是null。比如:

 Person[] persons = null;
//?.
int? length = persons?.Length;
//?[
Person first = persons?[];

3.2 ?.结合??

上面的persions?.Lenght返回的结果是Nullable<int>类型的,有时候我们可能需要的是一个int类型的,那么我们可以结合空连接运算符"??"一起使用,比如:

 Person[] persons = null;
//?.和??结合使用
int length = persons?.Length ?? ;

3.3 以线程安全的方式调用事件

 PropertyChangedEventHandler propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
}

上面的代码一直是我们调用事件的处理方式,把事件的引用放到一个临时变量中是为了防止在调用这个委托的时候,事件被取消注册,产生null的情况。

我们从C#6以后终于可以用更简单的方式去触发事件调用了(这个埂自从C#1时代一直延续至今...):

 PropertyChanged?.Invoke(propertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));

4. 总结

null条件运算符是一种语法简化,同时也会做一种编译优化,优化方式和三元运算符的优化效果是一致的。语法更简化了,性能也更好了,我们有什么理由不用新语法呢。

5. 参考

C#-Reference-Operators:Null-conditional Operators

[C#6] 3-null 条件运算符的更多相关文章

  1. C#NULL条件运算符

    C#6.0新增的特性 NULL条件运算符 ?. 之前我们在需要判断某个对象是否为空的是这样的 Person per = null; if (per != null) { Console.Write(& ...

  2. Unity中使用C#的null条件运算符?.的注意事项

    Introduction: 在C#6及以上版本中,加入了一项特别好用的运算符:Null条件运算符?.和?[]可以用来方便的执行判空操作,当运算符左侧操作数不为null时才会进行访问操作,否则直接返回n ...

  3. 探索C#之6.0语法糖剖析

    阅读目录: 自动属性默认初始化 自动只读属性默认初始化 表达式为主体的函数 表达式为主体的属性(赋值) 静态类导入 Null条件运算符 字符串格式化 索引初始化 异常过滤器when catch和fin ...

  4. [C#] 回眸 C# 的前世今生 - 见证 C# 6.0 的新语法特性

    回眸 C# 的前世今生 - 见证 C# 6.0 的新语法特性 序 目前最新的版本是 C# 7.0,VS 的最新版本为 Visual Studio 2017 RC,两者都尚未进入正式阶段.C# 6.0 ...

  5. C#与C++的发展历程第四 - C#6的新时代

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  6. 【转】C# 中 10 个你真的应该学习(和使用!)的功能

    如果你开始探索C#或决定扩展你的知识,那么你应该学习这些有用的语言功能,这样做有助于简化代码,避免错误,节省大量的时间. 1)async / await 使用async / await-pattern ...

  7. [C#6] 0-概览

    1. C#6 新特性图谱 C#6的新特性主要由编译器(“Roslyn”)带来,并不依赖CLR和.NET Framework的升级,我们可以认为这个版本的新语言特性的主要目的为了简化代码书写方式,让我们 ...

  8. C# 6.0

    C# 6.0 的新语法特性   回眸 C# 的前世今生 - 见证 C# 6.0 的新语法特性 序 目前最新的版本是 C# 7.0,VS 的最新版本为 Visual Studio 2017 RC,两者都 ...

  9. 从NullObject谈C#6.0改进

    前言 本文来聊一聊我们经常会做的空值检查问题,从一个简单的空值检查Any Where,到设计模式的NullObjectPattern,再到C#6.0“可能”会提供的语法,让我们体验一次语言开发上的“持 ...

随机推荐

  1. 让你的Mac支持NTFS

    前段时间换成Mac电脑之后,发现有一点不爽,不能在Mac下写入NTFS格式的磁盘,所以就去研究了一下. 解决方法有如下三种. 第一种,直接使用第三方软件,如Paragon NTFS for MAC,T ...

  2. [c++] Callable Objects

    Five kinds of callable objects: Functions Pointers to functions Objects of a class that overloads () ...

  3. Netty介绍

    Netty是由JBOSS开发的高性能.异步事件驱动的NIO框架,它提供了对TCP.UDP和文件传输等协议的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Li ...

  4. 回忆:#define的用法

    ANSI C规定:#前可以有空格或者tab,#和指令其余部分之间也可以有空格,可以出现在任何地方,作用域从定义处到文件结尾. 因为预处理开始前,系统会删除反斜线和换行符的组合,故可以把指令扩展到几个物 ...

  5. Color Transfer between Images code实现

    上计算机视觉课老师布置的作业实现论文:Color Transfer between Images 基本思路是: 1.给定srcImg和targetImg 2.将RGB空间转为Lab空间 3.根据论文中 ...

  6. java多线程--线程池的使用

    程序启动一个新线程的成本是很高的,因为涉及到要和操作系统进行交互,而使用线程池可以很好的提高性能,尤其是程序中当需要创建大量生存期很短的线程时,应该优先考虑使用线程池. 线程池的每一个线程执行完毕后, ...

  7. (二)探究本质,WebGIS前端地图显示之地图比例尺换算原理

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/. 1.没有豆浆机怎么办? 喝豆浆是我们早晨中基本必备的一环,油条豆浆,其 ...

  8. 简述ASP.NET MVC原理

    1.为什么ASP.NET需要MVC? 因为随着网站的的数量级越来越大,原始的网站方式,这里指的是WebForm,在运行速度和维护性方面,以及代码量上面,越来越难以满足日益庞大的网站维护成本.代码的重构 ...

  9. 【集合框架】JDK1.8源码分析之Comparable && Comparator(九)

    一.前言 在Java集合框架里面,各种集合的操作很大程度上都离不开Comparable和Comparator,虽然它们与集合没有显示的关系,但是它们只有在集合里面的时候才能发挥最大的威力.下面是开始我 ...

  10. 11.Configure Many-to-Many(配置多对多关系)【Code-First系列】

    现在学习EF Code-First多对多的配置. 这里我们举例:学生和班级实体,一个学生可以选修多个课程,多个学生也可以选修同一个课程. 一.使用数据注解特性,配置多对多的关系 using Syste ...