qué - ¿El orden de inicialización de clase estática en C#es determinista?
static java (4)
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("B.X = " + X);
X = A.X;
Console.WriteLine("B.X = " + X);
}
}
static class A
{
public static int X = B.X + 1;
static A() {
Console.WriteLine("A.X = " + X);
}
}
static class Program
{
static void Main() {
Console.WriteLine("A = {0}, B = {1}", A.X, B.X);
}
}
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 programa, 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
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 sintaxis 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.
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, tanstatic A()
llamado. -
AX
debe inicializarse, pero utilizaBX
, por lo que se llamastatic 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 astatic B()
.X
se imprime ("7"), luego se establece enAX
.A
ya comenzó a inicializarse, así que obtenemos el valor deAX
, 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 deAX
se establece enB.X+1
.AX = 1
. - Todos los campos estáticos de
A
se inicializan, por lo que se llamastatic A()
.AX
está impreso ("1"). - De vuelta en
Main
, se imprimen los valores deAX
yBX
("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.
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.