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<T>
. 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<T>
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).