[C#6] 3-null 条件运算符

0. 目录

C#6 新增特性目录

1. 老版本的代码

 1 namespace csharp6
 2 {
 3     internal class Person
 4     {
 5         public string Name { get; set; }
 6     }
 7 
 8     internal class Program
 9     {
10         private static void Main()
11         {
12             Person person = null;
13             //if判断
14             string name = null;
15             if (person != null)
16             {
17                 name = person.Name;
18             }
19         }
20     }
21 }

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

2. null条件运算符

 1 namespace csharp6
 2 {
 3     internal class Person
 4     {
 5         public string Name { get; set; }
 6     }
 7 
 8     internal class Program
 9     {
10         private static void Main()
11         {
12             Person person = null;
13             string name = person?.Name;
14         }
15     }
16 }

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

老版本的IL代码:

 1 .method private hidebysig static void  Main() cil managed
 2 {
 3   .entrypoint
 4   // Code size       23 (0x17)
 5   .maxstack  2
 6   .locals init ([0] class csharp6.Person person,
 7            [1] string name,
 8            [2] bool V_2)
 9   IL_0000:  nop
10   IL_0001:  ldnull
11   IL_0002:  stloc.0
12   IL_0003:  ldnull
13   IL_0004:  stloc.1
14   IL_0005:  ldloc.0
15   IL_0006:  ldnull
16   IL_0007:  cgt.un
17   IL_0009:  stloc.2
18   IL_000a:  ldloc.2
19   IL_000b:  brfalse.s  IL_0016
20   IL_000d:  nop
21   IL_000e:  ldloc.0
22   IL_000f:  callvirt   instance string csharp6.Person::get_Name()
23   IL_0014:  stloc.1
24   IL_0015:  nop
25   IL_0016:  ret
26 } // end of method Program::Main

新语法的IL:

 1 .method private hidebysig static void  Main() cil managed
 2 {
 3   .entrypoint
 4   // Code size       17 (0x11)
 5   .maxstack  1
 6   .locals init ([0] class csharp6.Person person,
 7            [1] string name)
 8   IL_0000:  nop
 9   IL_0001:  ldnull
10   IL_0002:  stloc.0
11   IL_0003:  ldloc.0
12   IL_0004:  brtrue.s   IL_0009
13   IL_0006:  ldnull
14   IL_0007:  br.s       IL_000f
15   IL_0009:  ldloc.0
16   IL_000a:  call       instance string csharp6.Person::get_Name()
17   IL_000f:  stloc.1
18   IL_0010:  ret
19 } // end of method Program::Main

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

 1 .method private hidebysig static void  Main() cil managed
 2 {
 3   .entrypoint
 4   // Code size       17 (0x11)
 5   .maxstack  1
 6   .locals init ([0] class csharp6.Person person,
 7            [1] string name)
 8   IL_0000:  nop
 9   IL_0001:  ldnull
10   IL_0002:  stloc.0
11   IL_0003:  ldloc.0
12   IL_0004:  brtrue.s   IL_0009
13   IL_0006:  ldnull
14   IL_0007:  br.s       IL_000f
15   IL_0009:  ldloc.0
16   IL_000a:  callvirt   instance string csharp6.Person::get_Name()
17   IL_000f:  stloc.1
18   IL_0010:  ret
19 } // end of method Program::Main

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

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

if版的IL分析:

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

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

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

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

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

3. Example

3.1 ?[

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

1 Person[] persons = null;
2 //?.
3 int? length = persons?.Length;
4 //?[
5 Person first = persons?[0];

3.2 ?.结合??

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

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

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

1 PropertyChangedEventHandler propertyChanged = PropertyChanged;
2 if (propertyChanged != null)
3 {
4    propertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
5 }

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

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

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

4. 总结

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏信安之路

php 弱类型问题

php 是一门简单而强大的语言,提供了很多 Web 适用的语言特性,其中就包括了变量弱类型,在弱类型机制下,你能够给一个变量赋任意类型的值。

17100
来自专栏猿人谷

Java初学者需掌握的30个概念

基本概念:       1.OOP中唯一关心的是对象的接口是什么,就像计算机的销售商她不管电源内部结构 是怎样的,他只关系能否给你提供电就行了,也就是只要知道c...

180100
来自专栏抠抠空间

集合 (set) 的增删改查及 copy()方法

简介: 集合是无序的,不重复的数据集合,它里面的元素是可哈希的(不可变类型),但是集合本身是不可哈希(所以集合做不了字典的键)的。以下是集合最重要的两点: 1、...

314110
来自专栏AI研习社

最常见的 35 个 Python 面试题及答案(2018 版)

作为一个 Python 新手,你必须熟悉基础知识。在本文中我们将讨论一些 Python 面试的基础问题和高级问题以及答案,以帮助你完成面试。包括 Python ...

87930
来自专栏九彩拼盘的叨叨叨

JavaScript 字符串练习题

如果对字符串的 API 不是很熟悉,可查阅 W3School JavaScript String API。

9410
来自专栏Golang语言社区

深入分析golang多值返回以及闭包的实现

一、前言 golang有很多新颖的特性,不知道大家的使用的时候,有没想过,这些特性是如何实现的?当然你可能会说,不了解这些特性好像也不影响自己使用golang,...

53660
来自专栏大闲人柴毛毛

稳扎稳打JavaScript(一)——作用域链内存模型

几个概念 在开始之前,先了解几个概念。 1.1. 作用域 作用域是指当前正在执行的代码能够访问到变量的范围; 每个函数都有各自的作用域,存储函数所有的局部变量...

48580
来自专栏java技术学习之道

Java异常详解及如何处理

25850
来自专栏JetpropelledSnake

Python入门之迭代器/生成器/yield的表达方式/面向过程编程

 本章内容     迭代器     面向过程编程       一、什么是迭代       二、什么是迭代器       三、迭代器演示和举例       四、生...

30890
来自专栏蜕变

Python 数据类型

Python主要数据类型包括list(列表)、tuple(元组)、dict(字典)和set(集合)等对象,下面逐一介绍这些Python数据类型。

9000

扫码关注云+社区

领取腾讯云代金券