C#6 null 条件运算符
1. 老版本的代码
namespace csharp6 { internal class Person { public string Name { get; set; } } internal class Program { private static void Main() { Person person = null; 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 2 .locals init ([0] class csharp6.Person person, [1] string name, [2] 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 1 .locals init ([0] class csharp6.Person person, [1] 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 1 .locals init ([0] class csharp6.Person person, [1] 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 2 .locals init ([0] class csharp6.Person person, //初始化局部变量person,把person放在索引为0的位置 [1] string name, //初始化局部变量name,把name放在索引为1的位置 [2] 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 1 .locals init ([0] class csharp6.Person person, //初始化局部变量person,把person放在索引为0的位置 [1] 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?[0];
3.2 ?.结合??
上面的persions?.Lenght返回的结果是Nullable类型的,有时候我们可能需要的是一个int类型的,那么我们可以结合空连接运算符"??"一起使用,比如:
Person[] persons = null;
//?.和??结合使用
int length = persons?.Length ?? 0;
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条件运算符是一种语法简化,同时也会做一种编译优化,优化方式和三元运算符的优化效果是一致的。语法更简化了,性能也更好了,我们有什么理由不用新语法呢。
上一篇:C#调用Java方法实例详解
栏 目:C#教程
本文标题:C#6 null 条件运算符
本文地址:https://www.xiuzhanwang.com/a1/C_jiaocheng/6352.html
您可能感兴趣的文章
- 01-10C#6.0中10大新特性的应用和总结
- 01-10C#在DataTable中根据条件删除某一行的实现方法
- 01-10C#检查foreach判读是否为null的方法
- 01-10使用C#语言实现的查询条件界面展开和收起功能
- 01-10C# #define条件编译详解
- 01-10C#如何将Access中以时间段条件查询的数据添加到ListView中
- 01-10C#中 Json 序列化去掉null值的方法
- 01-10C#6.0中你可能不知道的新特性总结
- 01-10C#中LINQ多条件JOIN时为什么可以使用匿名类
阅读排行
本栏相关
- 01-10C#通过反射获取当前工程中所有窗体并
- 01-10关于ASP网页无法打开的解决方案
- 01-10WinForm限制窗体不能移到屏幕外的方法
- 01-10WinForm绘制圆角的方法
- 01-10C#实现txt定位指定行完整实例
- 01-10WinForm实现仿视频播放器左下角滚动新
- 01-10C#停止线程的方法
- 01-10C#实现清空回收站的方法
- 01-10C#通过重写Panel改变边框颜色与宽度的
- 01-10C#实现读取注册表监控当前操作系统已
随机阅读
- 08-05织梦dedecms什么时候用栏目交叉功能?
- 01-11ajax实现页面的局部加载
- 08-05dedecms(织梦)副栏目数量限制代码修改
- 01-10SublimeText编译C开发环境设置
- 01-11Mac OSX 打开原生自带读写NTFS功能(图文
- 01-10C#中split用法实例总结
- 01-10delphi制作wav文件的方法
- 01-10使用C语言求解扑克牌的顺子及n个骰子
- 04-02jquery与jsp,用jquery
- 08-05DEDE织梦data目录下的sessions文件夹有什