时间:2021-05-20
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条件运算符
从上面我们可以看出,使用?. 这种方式可以代替if判断和简化三元运算符的使用,简洁到不能再简洁了吧。按照惯例,上两份IL代码对比对比。
老版本的IL代码:
if版的IL
新语法的IL:
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条件运算符是一种语法简化,同时也会做一种编译优化,优化方式和三元运算符的优化效果是一致的。语法更简化了,性能也更好了,我们有什么理由不用新语法呢。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
C#条件运算符是指什么呢?让我们首先从概念上了解C#条件运算符: ?:运算符称为条件运算符。有时,它也称为三元运算符。 conditional-expr
在C#中常用到的运算符有条件运算符,is运算符,as运算符,typeof运算符等等,接下来在文章中将为大家具体介绍各个运算符的使用方法条件运算符条件运算符用(&
注意啦,C#中的??运算符是和?:运算符是不同的,这两者是有区别的。 ??运算符称为null合并运算符,用于定义null值的类型和引用类型的默认值。 只
1.算术运算符 2.逻辑运算符 3.字符串运算符 4.位操作运算符 5.赋值运算符 6.条件运算符 7.其他运算符 注:除了条件运算符是三目运
C运算符运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C语言内置了丰富的运算符,并提供了以下类型的运算符:算术运算符关系运算符逻辑运算符位运算符赋值运算