I wouldn't use the word 'thread safe' to refer to this. Instead, I would ask the question, which of these is the same as the null coalesce operator?
get { return _bar != null ? _bar : new Object(); }
or
get
{
Object result = _bar;
if(result == null)
{
result = new Object();
}
return result;
}
From reading the other responses, it looks like it compiles to the equivalent to the second, not the first. As you noted, the first could return null, but the second one never will.
Is this thread safe? Technically, no. After reading _bar
, a different thread could modify _bar
, and the getter would return a value that's out of date. But from how you asked the question, I think this is what you're looking for.
Edit: Here's a way to do this that avoids the whole problem. Since value
is a local variable, it can't be changed behind the scenes.
public class Foo
{
Object _bar = new Object();
public Object Bar
{
get { return _bar; }
set { _bar = value ?? new Object(); }
}
}
Edit 2:
Here's the IL I see from a Release compile, with my interpretation of the 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.
}
Here's what I see from the other ways of doing it:
.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
}
In the IL, it's obvious that it's reading the _bar
field twice with the trinary operator, but only once with the null coalesce and the intermediate result var. In addition, the IL of the null coalesce method is very close to the intermediate result var method.
And here's the source I used to generate these:
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(); }
}