[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

if版的IL

新语法的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

null条件运算符版的IL

咦,貌似有很大不一样,我们再来一份三元运算符版的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

新语法"?."和三元运算符"?:"的结果是唯一的差别是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条件运算符是一种语法简化,同时也会做一种编译优化,优化方式和三元运算符的优化效果是一致的。语法更简化了,性能也更好了,我们有什么理由不用新语法呢。

5. 参考

C#-Reference-Operators:Null-conditional Operators

时间: 2024-10-12 19:13:55

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

C#NULL条件运算符

C#6.0新增的特性 NULL条件运算符 ?. 之前我们在需要判断某个对象是否为空的是这样的 Person per = null; if (per != null) { Console.Write(""); } 如果我们不这样判断的话,那么你就会得到一个 System.NullReferenceException  错误, 当然你也可以使用三元运算符 string str=person == null ? null :person.Name 但是在C#6.0的时候推出 NULL条件运算

null合并运算符【??】和null条件运算符【?.】

null合并运算符[??] 赋值的结果中的变量如果为空则用??后面的值替代前面的变量,否则直接用前面的变量 如果此运算符的左操作数不为 null,则此运算符将返回左操作数:否则返回右操作数 如: var result = a;if(a == null){ result = "";}//以上代码等同于 result = a?? ""; null条件运算符[?.] 用于在执行成员访问 (?.) 或索引 (?[) 操作之前,测试是否存在 NULL. 这些运算符可帮助编写更

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

Introduction: 在C#6及以上版本中,加入了一项特别好用的运算符:Null条件运算符?.和?[]可以用来方便的执行判空操作,当运算符左侧操作数不为null时才会进行访问操作,否则直接返回null.这极大的简化的判空代码的书写,但在使用过程中仍然需要注意一些问题,以免其带来我们意想不到的后果. 例如博主在使用Unity游戏引擎时便遇到使用下面代码依然会抛出空引用异常的情况: using UnityEngine; public class MyMonoBehaviour : MonoBe

C#6

C#6 1. 只读自动属性(Read-only auto-properties) C# 6之前我们构建只读自动属性: 1 public string FirstName { get; private set; } 2 public string LastName { get; private set; } 原理解析:就是编译器在生成set访问器时,它的修饰符是private,由上可知所谓的只读只是针对类外部,在类内部还是可以随意修改属性值的. C# 6中提供了真正的只读自动属性,写法如下: 1

有感 Visual Studio 2015 RTM 简介 - 八年后回归 Dot Net,终于迎来了 Mvc 时代,盼走了 Web 窗体时代

有感 Visual Studio 2015 RTM 简介 - 八年后回归 Dot Net,终于迎来了 Mvc 时代,盼走了 Web 窗体时代 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 关于

C# 6.0 新特性 (一)

概述 尽管 C# 6.0 尚未完成,但现在这些功能正处于接近完成的关键时刻.自 2014 年 5 月发布文章“C# 6.0 语言预览版”(msdn.microsoft.com/magazine/dn683793.aspx) 以来,下一版本的 Visual Studio 的 CTP3 版本中对 C# 6.0 进行了一些变更和改进(代号为“14”). Null 条件运算符 即使是 .NET 开发新手,也可能非常熟悉 NullReferenceException.有一个例外是几乎总是会指出一个 Bug

【转】30分钟掌握 C#6

[转]30分钟掌握 C#6 1. 只读自动属性(Read-only auto-properties) C# 6之前我们构建只读自动属性: 1 public string FirstName { get; private set; } 2 public string LastName { get; private set; } 原理解析:就是编译器在生成set访问器时,它的修饰符是private,由上可知所谓的只读只是针对类外部,在类内部还是可以随意修改属性值的. C# 6中提供了真正的只读自动属

Visual Studio 2017 RC 下载 最新版本的发行说明

我们非常荣幸地宣布 Visual Studio 2017 RC 现已推出! 此新版本包括我们最新的功能创新和改进. 注意 这里是 Visual Studio 2017 最新版本的发行说明. 下载:Visual Studio Enterprise 2017 RC 若要了解有关其他相关下载的详细信息,请参阅下载页. 另请参阅 Visual Studio 2017 系统要求和 Visual Studio 2017 平台目标以及兼容性. 重要事项 虽然一般情况下支持在生产环境中使用 Visual Stu

[C#6] 0-概览

1.新特性图谱 C#6的新特性主要有编译器(“Roslyn”)带来,并不依赖CLR和.NET Framework的升级,我们可以认为这个版本的新语言特性的主要目的为了简化代码书写方式,本质上是一些语法糖(编译器优化). 新特性如下: Using static成员 nameof表达式 string插值 Null 条件运算符 自动属性增强 表达式形式的成员函数 字典初始化器 异常增强 2.参考 C# 6.0 如何简化.阐明并压缩您的代码 New Language Features in C# 6