C# 6.0中的空条件运算符及其线程安全性

C#6.0中,引入了一种新的运算符——空条件运算符(null-conditional operator),它极大地减少了代码中难以调试和重现的空引用异常(NullReferenceException)的数量。最初,对这个运算符的理解仅限于它帮助进行链式空检查,尤其是在深入数据结构时,一旦链中的某个环节为null,就会短路剩余的检查。因此,最初认为它只是一种方便的语法糖。

然而,并没有完全理解这个运算符的真正强大之处。通常非常注重空检查,并且有很多C# 5.0的代码是这样的:

private static int GetCurrentSpeed(Car car) { if (car != null && car.Engine != null && car.Engine.ControlUnit != null) { return car.Engine.ControlUnit.CurrentSpeed; } return 0; }

一直在考虑是否值得重构代码库以使用空条件运算符。这样做会有什么好处吗?只是

那么,最好的图是什么呢?当然是IL代码的图。

因此,创建了这些简单的模型:

namespace CS6 { public class ControlUnit { public int CurrentSpeed { get; set; } } public class Engine { public ControlUnit ControlUnit { get; set; } } public class Car { public Engine Engine { get; set; } } }

以及两个控制台应用程序——一个在VS2013(C#5.0)中,另一个在VS2015(C# 6.0)中,它们显示在前面的代码片段中。(注意:在VS2015中,在项目属性中,可以指定要针对的C#编译器版本)。然后使用了JetBrains dotPeek来查看每个编译器生成的IL代码。

C# 5.0生成的IL代码:

可以点击图片查看代码和IL并排显示,但可能更喜欢C#5.0程序和IL Gist。

如果查看C# 5.0代码的第13行,访问car.Engine属性的代码,有相应的get_Engine()调用(IL中的第59行)。同样,在同一行访问car.Engine.ControlUnit属性的代码有相应的get_Engine()和get_ControlUnit()调用(IL中的第62和63行)。

第15行的car.Engine.ControlUnit.CurrentSpeed属性访问代码有相应的get_Engine()、get_ControlUnit()和get_CurrentSpeed()调用(IL中的第79至81行)。

这段代码的问题在于,运行它的线程随时可能被抢占,另一个线程可以为之前检查过的属性分配一个null值。当控制权返回到原始线程并且访问该属性时,就会抛出难以调试和难以重现的NullReferenceException。

C# 6.0生成的IL代码:

可以点击图片查看代码和IL并排显示,但可能更喜欢C# 6.0程序和IL Gist。

如果查看C# 6.0代码的第13行,使用空条件运算符访问三个属性的代码只有三个相应的get_Engine()、get_ControlUnit()和get_CurrentSpeed()调用(IL中的第64、72和80行),生成的代码更加健壮。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485