Is the null coalesce operator thread safe?

前端 未结 4 898
隐瞒了意图╮
隐瞒了意图╮ 2020-12-06 09:28

So this is the meat of the question: Can Foo.Bar ever return null? To clarify, can \'_bar\' be set to null after it\'s evaluated as non-null and before it\'s value is retur

4条回答
  •  南笙
    南笙 (楼主)
    2020-12-06 10:12

    No, this is not thread safe.

    The IL for the above compiles to:

    .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 
    }
    

    This effectively does a load of the _bar field, then checks its existence, and jumps ot the end. There is no synchronization in place, and since this is multiple IL instructions, it's possible for a secondary thread to cause a race condition - causing the returned object to differ from the one set.

    It's much better to handle lazy instantiation via Lazy. That provides a thread-safe, lazy instantiation pattern. Granted, the above code is not doing lazy instantiation (rather returning a new object every time until some time when _bar is set), but I suspect that's a bug, and not the intended behavior.

    In addition, Lazy makes setting difficult.

    To duplicate the above behavior in a thread-safe manner would require explicit synchronization.


    As to your update:

    The getter for the Bar property could never return null.

    Looking at the IL above, it _bar (via ldfld), then does a check to see if that object is not null using brtrue.s. If the object is not null, it jumps, copies the value of _bar from the execution stack to a local via stloc.0, and returns - returning _bar with a real value.

    If _bar was unset, then it will pop it off the execution stack, and create a new object, which then gets stored and returned.

    Either case prevents a null value from being returned. However, again, I wouldn't consider this thread-safe in general, since it's possible that a call to set happening at the same time as a call to get can cause different objects to be returned, and it's a race condition as which object instance gets returned (the set value, or a new object).

提交回复
热议问题