¿El orden de inicialización de clase estática en C # es determinista?

He hecho algunas búsquedas y creo que el siguiente código garantiza la producción:

BX = 7

BX = 0

AX = 1

A = 1, B = 0

static class B { public static int X = 7; static B() { Console.WriteLine("BX = " + X); X = AX; Console.WriteLine("BX = " + X); } } static class A { public static int X = BX + 1; static A() { Console.WriteLine("AX = " + X); } } static class Program { static void Main() { Console.WriteLine("A = {0}, B = {1}", AX, BX); } } 

He corrido esto varias veces y siempre obtengo el resultado por encima de la sección del código; Solo quería verificar que nunca cambiara? Incluso si textualmente, la clase A y la clase B se reorganizan?

¿Se garantiza que el primer uso de un objeto estático desencadenará la inicialización de sus miembros estáticos, y luego instanciará su constructor estático? Para este progtwig, usar AX en main activará la inicialización de AX, que a su vez inicializará BX, luego B () y luego de finalizar la inicialización de AX, pasará a A (). Finalmente, Main () emitirá AX y BX

Directamente desde ECMA-334:

17.4.5.1:Si existe un constructor estático (§17.11) en la clase, la ejecución de los inicializadores de campo estáticos ocurre inmediatamente antes de ejecutar ese constructor estático. De lo contrario, los inicializadores de campo estáticos se ejecutan en un tiempo dependiente de la implementación antes del primer uso de un campo estático de esa clase “.

Y:

17.11: La ejecución de un constructor estático se desencadena cuando se produce el primero de los siguientes eventos dentro de un dominio de aplicación:

  • Se crea una instancia de la clase.
  • Se hace referencia a cualquiera de los miembros estáticos de la clase.

Si una clase contiene el método principal (§10.1) en el que comienza la ejecución, el constructor estático para esa clase se ejecuta antes de que se llame al método principal. Si una clase contiene campos estáticos con inicializadores, esos inicializadores se ejecutan en orden textual inmediatamente antes de ejecutar el constructor estático (§17.4.5).

Entonces el orden es:

  • AX usado, tan static A() llamado.
  • AX debe inicializarse, pero utiliza BX , por lo que se llama static B() .
  • BX necesita ser inicializado, y se inicializa a 7. BX = 7
  • Todos los campos estáticos de B se inicializan, por lo que se llama a static B() . X se imprime (“7”), luego se establece en AX . A ya comenzó a inicializarse, así que obtenemos el valor de AX , que es el valor predeterminado (“cuando se inicializa una clase, todos los campos estáticos en esa clase se inicializan primero a su valor predeterminado”); BX = 0 , y está impreso (“0”).
  • Hecho inicializando B , y el valor de AX se establece en B.X+1 . AX = 1 .
  • Todos los campos estáticos de A se inicializan, por lo que se llama static A() . AX está impreso (“1”).
  • De vuelta en Main , se imprimen los valores de AX y BX (“1”, “0”).

De hecho, comenta sobre esto en el estándar:

17.4.5: Es posible que se observen campos estáticos con inicializadores variables en su estado de valor predeterminado. Sin embargo, esto se desaconseja fuertemente como una cuestión de estilo.

Alrededor de cuatro reglas diferentes en la especificación de C # están involucradas en hacer esta garantía, y es específica de C #. La única garantía hecha por el tiempo de ejecución de .NET es que la inicialización de tipos comienza antes de que se use el tipo.

  • Los campos estáticos se inicializan en cero hasta que se ejecuta el inicializador de tipo.
  • Los inicializadores de campo estáticos se ejecutan inmediatamente antes del constructor estático.
  • Que los constructores estáticos se llaman en la primera llamada de constructor de instancia o primera referencia de miembro estático.
  • Los argumentos de la función se evalúan en orden de izquierda a derecha.

Confiar en esto es una muy mala idea porque es probable que confunda a cualquiera que lea su código, especialmente si están familiarizados con idiomas con una syntax similar que no hacen las cuatro garantías anteriores.

Tenga en cuenta que el comentario de Porges estaba relacionado con mi afirmación inicial (basada en el comportamiento de .NET) de que las garantías son demasiado débiles para garantizar el comportamiento observado. Porges tiene razón en que las garantías son lo suficientemente fuertes, pero en realidad se trata de una cadena mucho más compleja de lo que sugiere.

Puede que le interese saber que incluso es posible asignar valores a un campo entre su inicialización predeterminada y la inicialización de la variable.

  private static int b = Foo(); private static int a = 4; private static int Foo() { Console.WriteLine(a); a = 3; Console.WriteLine(a); return 2; } public static void Main() { Console.WriteLine(a); } 

salidas

 0 3 4 

La inicialización determinística de miembros estáticos está de hecho garantizada … pero no está en “orden textual”. Además, es posible que no se realice de manera completamente perezosa (es decir, solo cuando se hace referencia por primera vez a la variable estática). Sin embargo, en su ejemplo con números enteros, no haría la diferencia.

En algunos casos, es deseable obtener una inicialización lenta (particularmente con los costosos Singletons), en cuyo caso a veces tiene que saltar algunos aros para hacerlo bien.