usar - variable de aplicacion c#
Al usar los inicializadores de objetos, ¿por qué el compilador genera una variable local adicional? (3)
Al responder ayer a una pregunta sobre SO, noté que si un objeto se inicializa con un Inicializador de objetos, el compilador crea una variable local adicional.
Considere el siguiente código C # 3.0, compilado en modo de lanzamiento en VS2008:
public class Class1
{
public string Foo { get; set; }
}
public class Class2
{
public string Foo { get; set; }
}
public class TestHarness
{
static void Main(string[] args)
{
Class1 class1 = new Class1();
class1.Foo = "fooBar";
Class2 class2 =
new Class2
{
Foo = "fooBar2"
};
Console.WriteLine(class1.Foo);
Console.WriteLine(class2.Foo);
}
}
Usando Reflector, podemos examinar el código para el método Principal:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 2
.locals init (
[0] class ClassLibrary1.Class1 class1,
[1] class ClassLibrary1.Class2 class2,
[2] class ClassLibrary1.Class2 <>g__initLocal0)
L_0000: newobj instance void ClassLibrary1.Class1::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: ldstr "fooBar"
L_000c: callvirt instance void ClassLibrary1.Class1::set_Foo(string)
L_0011: newobj instance void ClassLibrary1.Class2::.ctor()
L_0016: stloc.2
L_0017: ldloc.2
L_0018: ldstr "fooBar2"
L_001d: callvirt instance void ClassLibrary1.Class2::set_Foo(string)
L_0022: ldloc.2
L_0023: stloc.1
L_0024: ldloc.0
L_0025: callvirt instance string ClassLibrary1.Class1::get_Foo()
L_002a: call void [mscorlib]System.Console::WriteLine(string)
L_002f: ldloc.1
L_0030: callvirt instance string ClassLibrary1.Class2::get_Foo()
L_0035: call void [mscorlib]System.Console::WriteLine(string)
L_003a: ret
}
Aquí, podemos ver que el compilador ha generado dos referencias a una instancia de Class2
( class2
y <>g__initLocal0
), pero solo una referencia a una instancia de Class1
( class1
).
Ahora, no estoy muy familiarizado con IL, pero parece que está instanciando <>g__initLocal0
, antes de configurar class2 = <>g__initLocal0
.
¿Por qué pasó esto?
¿Sigue entonces, que hay una sobrecarga de rendimiento cuando se usan Inicializadores de objetos (incluso si es muy leve)?
La respuesta de Luke es correcta y excelente, tan buena contigo. No es, sin embargo, completo. Hay aún más buenas razones por las que hacemos esto.
La especificación es extremadamente clara de que este es el codegen correcto; la especificación dice que un inicializador de objetos crea un local temporal e invisible que almacena el resultado de la expresión. Pero, ¿por qué lo especificamos de esa manera? Es decir, ¿por qué
Foo foo = new Foo() { Bar = bar };
medio
Foo foo;
Foo temp = new Foo();
temp.Bar = bar;
foo = temp;
y no el más sencillo
Foo foo = new Foo();
foo.Bar = bar;
Bueno, como una cuestión puramente práctica, siempre es más fácil especificar el comportamiento de una expresión en función de su contenido, no su contexto. Sin embargo, para este caso específico, supongamos que especificamos que este era el codegen deseado para la asignación a un local o campo. En ese caso, foo se asignaría definitivamente después de (), y por lo tanto podría usarse en el inicializador. Realmente quieres
Foo foo = new Foo() { Bar = M(foo) };
ser legal? Espero que no. Foo no está definitivamente asignado hasta después de que la inicialización haya terminado.
O bien, considere las propiedades.
Frob().MyFoo = new Foo() { Bar = bar };
Esto tiene que ser
Foo temp = new Foo();
temp.Bar = bar;
Frob().MyFoo = temp;
y no
Frob().MyFoo = new Foo();
Frob().MyFoo.Bar = bar;
porque no queremos que Frob () llame dos veces y no queremos que MyFoo tenga acceso a la propiedad dos veces, queremos que cada una de ellas se acceda una vez.
Ahora, en su caso particular, podríamos escribir un pase de optimización que detecte que el extra local es innecesario y optimizarlo. Pero tenemos otras prioridades, y el jitter probablemente hace un buen trabajo optimizando a los locales.
Buena pregunta. He querido bloguear este por un tiempo.
Por el Por qué: ¿podría ser que se haya hecho para garantizar que no haya ninguna referencia "conocida" a un objeto no inicializado (por completo) (desde el punto de vista del lenguaje)? Algo así como (pseudo-) semántica de constructor para el inicializador de objetos? Pero eso es solo una idea ... y no puedo imaginar una forma de usar la referencia y acceder al objeto no inicializado además de en un entorno de subprocesos múltiples.
EDITAR: demasiado lento ...
Seguridad y atomicidad del hilo.
Primero, considere esta línea de código:
MyObject foo = new MyObject { Name = "foo", Value = 42 };
Cualquiera que lea esa afirmación podría asumir razonablemente que la construcción del objeto foo
será atómica. Antes de la asignación, el objeto no existe en absoluto. Una vez que la tarea se ha completado, el objeto existe y se encuentra en el estado esperado.
Ahora considere dos maneras posibles de traducir ese código:
// #1
MyObject foo = new MyObject();
foo.Name = "foo";
foo.Value = 42;
// #2
MyObject temp = new MyObject(); // temp will be a compiler-generated name
temp.Name = "foo";
temp.Value = 42;
MyObject foo = temp;
En el primer caso, el objeto foo
se instancia en la primera línea, pero no estará en el estado esperado hasta que la línea final haya terminado de ejecutarse. ¿Qué sucede si otro hilo intenta acceder al objeto antes de que se ejecute la última línea? El objeto estará en un estado semi-inicializado.
En el segundo caso, el objeto foo
no existe hasta la línea final cuando se asigna desde temp
. Como la asignación de referencia es una operación atómica, proporciona exactamente la misma semántica que la declaración de asignación original de una sola línea. es decir, el objeto foo
nunca existe en un estado semiautomatizado.