null coalesce操作符线程是否安全?

所以这就是问题的关键:Foo.Bar可以返回null吗? 为了澄清,’_bar’在被评估为非null并且返回它之前可以设置为null吗?

public class Foo { Object _bar; public Object Bar { get { return _bar ?? new Object(); } set { _bar = value; } } } 

我知道使用以下get方法并不安全,并且可以返回null值:

  get { return _bar != null ? _bar : new Object(); } 

更新:

另一种看待同一问题的方法,这个例子可能更清楚:

  public static T GetValue(ref T value) where T : class, new() { return value ?? new T(); } 

再次询问GetValue(…)是否会返回null? 根据你的定义,这可能是也可能不是线程安全的……我猜正确的问题陈述是询问它是否是一个关于价值的primefaces操作…… David Yaw已经通过说上面的函数等效来定义问题了以下内容:

  public static T GetValue(ref T value) where T : class, new() { T result = value; if (result != null) return result; else return new T(); } 

不,这不是线程安全的。

上述IL编译为:

 .method public hidebysig specialname instance object get_Bar() cil managed { .maxstack 2 .locals init ( [0] object CS$1$0000) L_0000: nop L_0001: ldarg.0 L_0002: ldfld object ConsoleApplication1.Program/MainClass::_bar L_0007: dup L_0008: brtrue.s L_0010 L_000a: pop L_000b: newobj instance void [mscorlib]System.Object::.ctor() L_0010: stloc.0 L_0011: br.s L_0013 L_0013: ldloc.0 L_0014: ret } 

这有效地加载_bar字段,然后检查它的存在,并跳转到结尾。 没有同步,因为这是多个IL指令,所以辅助线程可能会导致竞争条件 – 导致返回的对象与一组不同。

通过Lazy处理延迟实例化会好得多。 这提供了一个线程安全的,懒惰的实例化模式。 当然,上面的代码没有进行延迟实例化(而是每次都返回一个新对象,直到设置_bar某个时间),但我怀疑这是一个错误,而不是预期的行为。

另外, Lazy使设置变得困难。

要以线程安全的方式复制上述行为,需要显式同步。


至于你的更新:

Bar属性的getter永远不会返回null。

查看上面的IL,它是_bar (通过ldfld),然后使用brtrue.s检查该对象是否为null。 如果对象不为null,则跳转,将_bar的值从执行堆栈复制到本地via stloc.0 ,并返回 – 返回带有实际值的_bar

如果_bar设置_bar ,则会将其从执行堆栈中弹出,然后创建一个新对象,然后存储并返回该对象。

这两种情况都会阻止返回null值。 但是,我一般不会认为这个线程安全,因为调用set可能会在调用get时同时调用可能导致返回不同的对象,并且这是一个竞争条件返回实例(设置值或新对象)。

我不会用’thread safe’这个词来引用它。 相反,我会问这个问题,哪一个与null coalesce运算符相同?

 get { return _bar != null ? _bar : new Object(); } 

要么

 get { Object result = _bar; if(result == null) { result = new Object(); } return result; } 

通过阅读其他响应,看起来它编译为等效于第二个,而不是第一个。 如你所知,第一个可以返回null,但第二个永远不会。

这个线程安全吗? 从技术上讲,没有。 在阅读_bar ,另一个线程可以修改_bar ,并且getter将返回一个过时的值。 但是从你提问的方式来看,我认为这正是你所寻找的。

编辑:这是一种避免整个问题的方法。 由于value是局部变量,因此无法在幕后更改。

 public class Foo { Object _bar = new Object(); public Object Bar { get { return _bar; } set { _bar = value ?? new Object(); } } } 

编辑2:

这是我从Release编译中看到的IL,以及我对IL的解释。

 .method public hidebysig specialname instance object get_Bar_NullCoalesce() cil managed { .maxstack 8 L_0000: ldarg.0 // Load argument 0 onto the stack (I don't know what argument 0 is, I don't understand this statement.) L_0001: ldfld object CoalesceTest::_bar // Loads the reference to _bar onto the stack. L_0006: dup // duplicate the value on the stack. L_0007: brtrue.s L_000f // Jump to L_000f if the value on the stack is non-zero. // I believe this consumes the value on the top of the stack, leaving the original result of ldfld as the only thing on the stack. L_0009: pop // remove the result of ldfld from the stack. L_000a: newobj instance void [mscorlib]System.Object::.ctor() // create a new object, put a reference to it on the stack. L_000f: ret // return whatever's on the top of the stack. } 

这是我从其他方式看到的:

 .method public hidebysig specialname instance object get_Bar_IntermediateResultVar() cil managed { .maxstack 1 .locals init ( [0] object result) L_0000: ldarg.0 L_0001: ldfld object CoalesceTest::_bar L_0006: stloc.0 L_0007: ldloc.0 L_0008: brtrue.s L_0010 L_000a: newobj instance void [mscorlib]System.Object::.ctor() L_000f: stloc.0 L_0010: ldloc.0 L_0011: ret } .method public hidebysig specialname instance object get_Bar_TrinaryOperator() cil managed { .maxstack 8 L_0000: ldarg.0 L_0001: ldfld object CoalesceTest::_bar L_0006: brtrue.s L_000e L_0008: newobj instance void [mscorlib]System.Object::.ctor() L_000d: ret L_000e: ldarg.0 L_000f: ldfld object CoalesceTest::_bar L_0014: ret } 

在IL中,很明显它使用三元运算符两次读取_bar字段,但只有一次使用null coalesce和中间结果var。 另外,零合并方法的IL非常接近中间结果var方法。

这是我用来生成这些的源代码:

 public object Bar_NullCoalesce { get { return this._bar ?? new Object(); } } public object Bar_IntermediateResultVar { get { object result = this._bar; if (result == null) { result = new Object(); } return result; } } public object Bar_TrinaryOperator { get { return this._bar != null ? this._bar : new Object(); } } 

getter永远不会返回null

这是因为当对变量_bar )执行读取时,将计算表达式,然后结果对象(或null)将_bar “空闲” 变量_bar )。 这是第一次评估的结果,然后“传递”给合并操作员。 (参见里德对IL的好答案。)

但是, 这不是线程安全的,并且由于与上述相同的原因,分配很容易丢失。

reflection器说不:

 List l = null; var x = l ?? new List(); 

编译为:

 [STAThread] public static void Main(string[] args) { List list = null; if (list == null) { new List(); } } 

在您提到的方面,这似乎不是线程安全的。