c# - logico - ¿Es seguro el hilo del operador de fusión nula?
o logico c# (4)
No usaría la palabra ''hilo seguro'' para referirme a esto. En cambio, me gustaría preguntar, ¿cuál de estos es el mismo que el operador de fusión nula?
get { return _bar != null ? _bar : new Object(); }
o
get
{
Object result = _bar;
if(result == null)
{
result = new Object();
}
return result;
}
Al leer las otras respuestas, parece que se compila al equivalente al segundo, no al primero. Como notaste, el primero podría regresar nulo, pero el segundo nunca lo hará.
¿Es seguro este hilo? Técnicamente, no. Después de leer _bar
, un hilo diferente podría modificar _bar
, y el getter devolvería un valor que está desactualizado. Pero de cómo hiciste la pregunta, creo que esto es lo que estás buscando.
Edición: Aquí hay una manera de hacer esto que evita todo el problema. Como el value
es una variable local, no se puede cambiar detrás de escena.
public class Foo
{
Object _bar = new Object();
public Object Bar
{
get { return _bar; }
set { _bar = value ?? new Object(); }
}
}
Edición 2:
Aquí está el IL que veo en una compilación de Release, con mi interpretación del 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 la IL, es obvio que está leyendo el campo _bar
dos veces con el operador trinario, pero solo una vez con la unión nula y el resultado intermedio var. Además, la IL del método de fusión nula está muy cerca del método var de resultado intermedio.
Y aquí está la fuente que utilicé para generar estos:
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(); }
}
Así que esta es la carne de la pregunta: ¿Puede Foo.Bar volver nula? Para aclarar, ¿se puede establecer ''_bar'' en nulo después de que se evalúe como no nulo y antes de que se devuelva su valor?
public class Foo
{
Object _bar;
public Object Bar
{
get { return _bar ?? new Object(); }
set { _bar = value; }
}
}
Sé que el uso del siguiente método de obtención no es seguro y puede devolver un valor nulo:
get { return _bar != null ? _bar : new Object(); }
ACTUALIZAR:
Otra forma de ver el mismo problema, este ejemplo podría ser más claro:
public static T GetValue<T>(ref T value) where T : class, new()
{
return value ?? new T();
}
Y de nuevo preguntando, ¿puede GetValue (...) volver nulo? Dependiendo de su definición, esto puede o no puede ser seguro para subprocesos ... Supongo que la declaración del problema correcto es preguntar si se trata de una operación atómica en valor ... David Yaw ha definido la pregunta mejor diciendo que la función anterior es la función equivalente. a lo siguiente:
public static T GetValue<T>(ref T value) where T : class, new()
{
T result = value;
if (result != null)
return result;
else
return new T();
}
No, esto no es seguro para subprocesos.
El IL por lo anterior compila a:
.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
}
Esto efectivamente hace una carga del campo _bar
, luego verifica su existencia y salta al final. No hay sincronización en su lugar y, dado que se trata de varias instrucciones de IL, es posible que un subproceso secundario provoque una condición de carrera, lo que hace que el objeto devuelto difiera del conjunto establecido.
Es mucho mejor manejar la instanciación perezosa a través de msdn.microsoft.com/en-us/library/dd642331.aspx . Eso proporciona un patrón de creación de instancias perezoso y seguro para subprocesos. Por supuesto, el código anterior no está haciendo una instanciación perezosa (en lugar de devolver un objeto nuevo cada vez hasta que se establece un _bar
), pero sospecho que es un error y no el comportamiento previsto.
Además, Lazy<T>
dificulta la configuración.
Para duplicar el comportamiento anterior de una manera segura para subprocesos se requeriría una sincronización explícita.
En cuanto a su actualización:
El captador de la propiedad de Bar nunca podría volver nulo.
Mirando el IL de arriba, _bar
(a través de ldfld), luego verifica si ese objeto no es nulo usando brtrue.s . Si el objeto no es nulo, salta, copia el valor de _bar
de la pila de ejecución a un local a través de stloc.0 , y devuelve - devolviendo _bar
con un valor real.
Si _bar
estaba desarmado, lo sacará de la pila de ejecución y creará un nuevo objeto, que luego se almacena y devuelve.
Cualquiera de los casos impide que se devuelva un valor null
. Sin embargo, una vez más, no consideraría este hilo seguro en general, ya que es posible que una llamada a establecer suceda al mismo tiempo que una llamada a obtener pueda hacer que se devuelvan diferentes objetos, y es una condición de carrera como qué objeto la instancia se devuelve (el valor establecido, o un nuevo objeto).
Reflector dice que no:
List<int> l = null;
var x = l ?? new List<int>();
Compila a:
[STAThread]
public static void Main(string[] args)
{
List<int> list = null;
if (list == null)
{
new List<int>();
}
}
Lo que no parece ser seguro para subprocesos en el respeto que ha mencionado.
El getter nunca volverá nulo .
Esto se debe a que cuando la lectura se realiza en la variable ( _bar
), la expresión se evalúa y el objeto resultante (o nulo) está entonces "libre" de la variable ( _bar
). Es el resultado de esta primera evaluación que luego se "pasa" al operador de fusión. (Vea la buena respuesta de Reed para la IL).
Sin embargo, esto no es seguro para subprocesos y una asignación puede perderse fácilmente por la misma razón que la anterior.