No utilizaría la palabra 'hilo seguro' para referirse a esto. En cambio, me gustaría hacer la pregunta, ¿cuál de estos es el mismo que el operador de fusión nulo?
get { return _bar != null ? _bar : new Object(); }
o
get
{
Object result = _bar;
if(result == null)
{
result = new Object();
}
return result;
}
De la lectura de las otras respuestas, parece que se compila en el equivalente a la segunda, no el primero. Como notó, el primero podría devolver nulo, pero el segundo nunca lo hará.
¿Este thread es seguro? Técnicamente, no. Después de leer _bar
, un hilo diferente podría modificar _bar
, y el captador devolvería un valor que está desactualizado. Pero por la forma en que hizo la pregunta, creo que esto es lo que está buscando.
Editar: Aquí hay una manera de hacer esto que evita todo el problema. Como value
es una variable local, no se puede cambiar entre bastidores.
public class Foo
{
Object _bar = new Object();
public Object Bar
{
get { return _bar; }
set { _bar = value ?? new Object(); }
}
}
Edición 2:
Aquí está la IL veo de una compilación de lanzamiento, con mi interpretación de la 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.
}
Esto es lo que veo de las otras maneras de hacerlo:
.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
}
En el IL, es obvio que se está leyendo el campo _bar
dos veces con el operador ternario, pero sólo una vez con el nula se unen y el resultado intermedio var. Además, el IL del método de fusión nulo está muy cerca del método de var de resultado intermedio.
Y aquí está la fuente que utiliza para generar los siguientes:
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(); }
}
Se puede utilizar la clase genérica Lazy lugar para inicializar de forma segura. http://msdn.microsoft.com/en-us/library/dd642331.aspx – TrueWill
@TrueWill: es difícil usar Lazy cuando necesita tener un setter en la propiedad, también ... –
Encontrado esto: http://haacked.com/archive/2006/08/08/IsTheNullCoalescingOperatorThreadSafe.aspx –