2012-10-10 32 views
5

Si un tipo no tiene constructor estático, inicializadores de campo se ejecutarán justo antes de que el tipo de ser utiliza- o en cualquier momento anterior a capricho de el tiempo de ejecuciónconstructores estáticos y BeforeFieldInit?

¿Por qué este código:

void Main() 
{ 
    "-------start-------".Dump(); 
    Test.EchoAndReturn("Hello"); 
    "-------end-------".Dump(); 

} 

class Test 
{ 
    public static string x = EchoAndReturn ("a"); 
    public static string y = EchoAndReturn ("b"); 
    public static string EchoAndReturn (string s) 
    { 
     Console.WriteLine (s); 
     return s; 
    } 
} 

rendimientos:

-------start------- 
a 
b 
Hello 
-------end------- 

mientras que este código:

void Main() 
{ 
    "-------start-------".Dump(); 
    var test=Test.x; 
    "-------end-------".Dump(); 

} 

produce

a 
b 
-------start------- 
-------end------- 

El orden de a y b se entiende. pero ¿por qué lidiar con static method es diferente que static field.

me refiero a por qué el inicio y final líneas están en diferentes ubicaciones con métodos estáticos frente a campos estáticos? Quiero decir, en ambas situaciones él tiene que inicializar esos campos ... ¿por qué?

(Sé que puedo añadir ctor estática que hacen que sea la misma -., Pero estoy preguntando por esta situación particular)

(ps Dump() es igual Console.Write)

+2

debería tener en cuenta que si me desplazo sobre sus preguntas, puedo crear una buena base para la RRHH Reclutamiento :) – Tigran

+1

@Tigran Sí. Lo sé. Siempre me gustan las preguntas de borde. No me preguntes por qué. (Puede estar seguro de que no estoy tratando con este tipo de cosas en mi trabajo). :-) –

+0

¿Estás en depuración o versión? el comportamiento puede ser diferente –

Respuesta

6

El comportamiento del release JIT es (desde 4.0 IIRC) no ejecutar el inicializador estático a menos que el método que está llamando toque campos estáticos. Esto puede significar que los campos estáticos no son inicializados. Si funciono el primer código de liberación fuera del depurador, me sale:

-------start------- 
Hello 
-------end------- 

si funciono con el depurador asociado (liberación), o en una versión de depuración (con o sin depurador adjunta), consigo :

-------start------- 
a 
b 
Hello 
-------end------- 

Hasta ahora, tan interesante. Por qué se obtiene:

a 
b 
-------start------- 
-------end------- 

parece que el JIT por cada método se toma esencialmente la responsabilidad de ejecutar el constructor estático en este escenario. Se puede ver esto añadiendo:

if(NeverTrue()) { // method that returns false 
     "-------start-------".Dump(); 
     var test = Test.x; 
     "-------end-------".Dump(); 
} 

que imprimirá (incluso en la liberación sin el depurador)

a 
b 

por lo que la posibilidad campos de acceso es la clave. Si cambiamos Test.x para que sea una llamada a un método que no accede a los campos (y elimina la cosa NeverTrue()), entonces obtenemos sin salida alguna.

Por lo tanto: en algunas versiones de la CLI, la ejecución de inicializadores estáticos puede diferirse al paso JIT de métodos que contienen menciones a cualquier campo (no verifica si ese campo tiene un inicializador).

Incluso podemos crear instancias de objetos sin correr el inicializador estático, siempre y cuando no tocamos los campos estáticos:

public Test() 
{ 
    a = ""; 
} 
string a; 

con:

"-------start-------".Dump(); 
new Test(); 
"-------end-------".Dump(); 

impresiones solo (liberación, sin depurador):

-------start------- 
-------end------- 

SIN EMBARGO! No hay que construir cualquier cosa que depende de este momento:

  • cambia entre la versión .NET
  • bien puede cambiar entre la plataforma (x86, x64, CF, SL, .NETCore, etc)
  • se puede cambiar dependiendo de si el depurador está conectado y si se trata de una depuración/release construir
+0

por lo que todo este antes de Campo necesita ser considerado de manera diferente con las versiones de depuración/versión? –

+1

@RoyiNamir ejecutando un exe de consola de diferentes maneras, parece que la respuesta es un "sí" sólido –

+0

La especificación básicamente indica que los campos se inicializarán "en un momento dependiente de la implementación" antes de que se utilicen. El punto clave que hay que tener en cuenta aquí es que * no garantiza en absoluto * la posposición de la inicialización hasta que se necesite. Podría ejecutar todos los inicializadores de campo estáticos en todas las clases antes de que ingrese Main() y aún así seguiría siendo compatible. La especificación no aplica la pereza, simplemente lo permite. – AnorZaken

3

el momento en el que se llamará Constuctor estático no está garantizada, por lo que para el programm es como un comportamiento indefinido en C++. Nadie debería confiar en la secuencia de llamadas constructor estáticas. Por ejemplo, si compila el programa bajo release, verá que el counstructor estático lo llamó al mismo tiempo en ambos casos.

+0

Bueno, no del todo. La especificación de C# es mucho más rígida, y en la mayoría de los casos puede hacer varias garantías sobre el resultado. Suponiendo que encapsula de cierta manera e inicializa de cierta manera, la especificación es lo suficientemente fuerte como para garantizar el resultado exacto. Básicamente en C# no puede estar seguro de lo que sucede en el caso general, pero puede diseñar su inicialización estática de manera que cree sólidas garantías. Mientras que en C++ es solo un nido de ratas indefinido de pura suerte. (La especificación C# tiene ejemplos que muestran el resultado que debe esperar si tiene dependencias circulares). – AnorZaken

1

esto es con .NET 4.0

Si un tipo no tiene constructor estático, sino que los campos estáticos con la inicialización, el compilador crea un constructor de tipos y pone la inicialización en su interior.

class Test 
    { 
     public static string x = EchoAndReturn("a"); 
     public static string y = EchoAndReturn("b"); 
     public static string EchoAndReturn(string s) 
     { 
      Console.WriteLine(s); 
      return s; 
     } 
    } 

resultados en las siguientes IL (sólo la parte cctor)

.method private hidebysig specialname rtspecialname static 
     void .cctor() cil managed 
{ 
    // Code size  31 (0x1f) 
    .maxstack 8 
    IL_0000: ldstr  "a" 
    IL_0005: call  string ConsoleApplication1.Test::EchoAndReturn(string) 
    IL_000a: stsfld  string ConsoleApplication1.Test::x 
    IL_000f: ldstr  "b" 
    IL_0014: call  string ConsoleApplication1.Test::EchoAndReturn(string) 
    IL_0019: stsfld  string ConsoleApplication1.Test::y 
    IL_001e: ret 
} // end of method Test::.cctor 

También, según CLR via C#, los compilador JIT de cheques cada método de antemano, qué tipos tienen un constructor estático. Si no se ha llamado al constructor estático, el compilador JIT lo llama.

Eso explicaría la diferencia entre los dos fragmentos de código.

When the just-in-time (JIT) compiler is compiling a method, 

se ve qué tipos se hace referencia en el código .Si cualquiera de los tipos definen un constructor de tipos, los controles del compilador JIT si constructor de tipos del tipo que ya se ha ejecutado para este dominio de aplicación .Si el constructor nunca se ha ejecutado, el compilador JIT emite una llamada al constructor de tipo en el código nativo que el compilador JIT está emitiendo .

@Comment

Si se mueve un inicializador de campo en un constructor de tipo definido por el usuario, el compilador se moverá el otro campo de inicializar en el nivel de clase en el constructor de tipos también.

static Test() 
{ 
    y = EchoAndReturn("b"); 
} 

Resultados en la misma IL que anteriormente. Entonces, básicamente, no hay diferencia entre tu propio constructor de tipos y el generado por el compilador (de todos modos, solo puede haber uno).

+0

¿Entonces crea un ctor "falso"? en caso afirmativo, ¿cuál es la diferencia entre crear su propio ctor frente a un ctor listo? –

+0

@RoyiNamir ha agregado una respuesta. No estoy seguro de lo que quiere decir con listo ctor. También tenga en cuenta que el constructor estático es un "cctor" mientras que el constructor de la instancia es un "ctor". – Alex

Cuestiones relacionadas