本文将分享奇怪的Null指针异常情况:三元条件运算符不适用于字符串连接的详细内容,并且还将对java三元表达式null判断进行详尽解释,此外,我们还将为大家带来关于'?:'的返回类型(三元条件运算符)
本文将分享奇怪的Null指针异常情况:三元条件运算符不适用于字符串连接的详细内容,并且还将对java三元表达式null判断进行详尽解释,此外,我们还将为大家带来关于'?:' 的返回类型(三元条件运算符)、C# NULL 值条件运算符?.、C#6 null 条件运算符、C#NULL 条件运算符的相关知识,希望对你有所帮助。
本文目录一览:- 奇怪的Null指针异常情况:三元条件运算符不适用于字符串连接(java三元表达式null判断)
- '?:' 的返回类型(三元条件运算符)
- C# NULL 值条件运算符?.
- C#6 null 条件运算符
- C#NULL 条件运算符
奇怪的Null指针异常情况:三元条件运算符不适用于字符串连接(java三元表达式null判断)
StringBuffer sb=null;// Some more logic that conditionally assigns value to the StringBuffer// Prints Value=nullSystem.out.println("Value="+sb);// Throws NullPointerExceptionSystem.out.println("Value=" + sb != null ? sb.toString() : "Null");
解决此问题的方法是将三元运算符包含在方括号中:
// Works fineSystem.out.println("Value=" + (sb != null ? sb.toString() : "Null"));
这怎么可能?
答案1
小编典典一个+
具有更高的优先级比!=
。
因此,您起初会进行评估"(Value="+sb ) != null
。
'?:' 的返回类型(三元条件运算符)
为什么第一个返回引用?
int x = 1;int y = 2;(x > y ? x : y) = 100;
而第二个没有?
int x = 1;long y = 2;(x > y ? x : y) = 100;
实际上,第二个根本没有编译 - “不是左值赋值”。
答案1
小编典典表达式没有返回类型,它们有一个类型和 - 正如最新的 C++ 标准中所知道的 - 一个值类别。
条件表达式可以是 左值 或 右值 。这是它的价值范畴。(这有点简化,C++11
因为我们有左值、xvalues 和 prvalues。)
用非常广泛和简单的术语来说, 左值 指的是内存中的对象,而 右值 只是一个可能不一定附加到内存中的对象的值。
赋值表达式为对象赋值,因此被赋值的事物必须是 左值 。
要使条件表达式 ( ?:
) 成为 左值 (同样,从广义和简单的角度来说), 第二个和第三个操作数必须是 相同
类型的左值。这是因为条件表达式的类型和值类别是在编译时确定的,并且无论条件是否为真都必须是适当的。如果必须将其中一个操作数转换为不同的类型以匹配另一个,则条件表达式不能是
左值 ,因为此转换的结果不会是 左值 。
ISO/IEC 14882:2011 参考:
3.10 [basic.lval] 左值和右值(关于值类别)
5.15 [expr.cond] 条件运算符(条件表达式具有什么类型和值类别的规则)
5.17 [expr.ass] 赋值和复合赋值运算符(要求赋值的 lhs 必须是可修改的左值)
C# NULL 值条件运算符?.
Null 值条件运算符属于 C#6.0 的语法糖
使用方法:变量名?. 属性
下列 Person 为使用到的类
1 public class Person
2 {
3 public string Name { get; set; }
4
5 public int Age { get; set; }
6
7 public int? Height { get; set; }
8 }
如果使用下列调用方式,则会引起 NullReferenceException 异常
为了容错,我们一般会在代码里写这样的判断
1 Person p = null;
2 if (p!=null)
3 {
4 string pName = p.Name;
5 }
在 C#6.0 以后推出了 Null 值条件运算符,我们可以简化成这样的写法
1 Person p = null;
2 string pName = p?.Name;
当然我们也可以使用三元运算符来做判断
1 string pName = p == null ? null : p.Name;
我在项目中经常使用到该运算符,在一些带有释放资源的 finally 代码块中使用会引起非常的舒适
如果项目中支持 C#6.0 那么最好是优先使用此方法,与 if 使用方式来说两者的 IL 代码是不同的,对该运算符的 IL 代码有想深入了解请参考此博客 https://www.cnblogs.com/linianhui/p/csharp6_null-conditional-operators.html#auto_id_1
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放在索引为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#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 条件运算符
Person per = null;
string name = per?.Name;
如果我们这样写的话,同样也可以判断是否为空,代替了 if 的判断和简化了三元运算符的操作
例子:
我们尝试一下,在一个集合中
List<Person> person = new List<Person>()
{
new Person{ Name="张三",Age=12},
new Person{ Name="李四",Age=12},
new Person{ Name=null,Age=13},
new Person{ Name="张三",Age=12},
};
我们需要查找姓是李的人,如果我们不使用 NULL 条件运算符,结果是..
不出意料,因为 Name 中存在 NUll 值,所以得到了一个 System.NullReferenceException
这次我们加上 NULL 条件运算符,结果...
加上 NULL 条件运算符之后,返回类型发生了改变,变成了 Nullable<T>, 如果我们还需要原来的类型,只需要和空运算符一起使用就可以了
因为返回的是 bool 类型,那我们看一下 false 返回的是什么
就会返回符合条件的值
如果为 true 又会返回什么呢
我们可以清晰的可以看到,为 true 的时候,不仅返回符合条件的数据,同时也返回了 Name 为空的值
?[
NULL 运算符不但可以使用 ?. 访问对象的属性和方法,还可以使用 ?[监测数组或包含索引器的对象是否为空
int[] are = new int[5] { 2, 3, 5, 7, 4 };
int? value = are?.Length;
int? result = are?[0];
NULL 条件运算符返回的类型是 Nullable<T>, 我们不想要这样的类型的话需要配合??空运算符使用
?? 空运算符
int[] are = new int[5] { 2, 3, 5, 7, 4 };
int value = are?.Length??0;
int result = are?[0]??0;
今天关于奇怪的Null指针异常情况:三元条件运算符不适用于字符串连接和java三元表达式null判断的介绍到此结束,谢谢您的阅读,有关'?:' 的返回类型(三元条件运算符)、C# NULL 值条件运算符?.、C#6 null 条件运算符、C#NULL 条件运算符等更多相关知识的信息可以在本站进行查询。
本文标签: