transición realizar podido desactivar contexto c# struct clr static-constructor typeinitializer

c# - realizar - desactivar contextswitchdeadlock



¿Por qué el CLR no siempre llama constructores de tipo de valor? (7)

Tengo una pregunta sobre los constructores de tipos dentro de un tipo de valor . Esta pregunta se inspiró en algo que Jeffrey Richter escribió en CLR a través de C # 3rd ed., Dice (en la página 195 - capítulo 8) que nunca se debe definir un constructor de tipos dentro de un tipo de valor, ya que hay momentos en que CLR no llama eso.

Entonces, por ejemplo (bueno ... ejemplo de Jeffrey Richters en realidad), no puedo resolver, incluso mirando a IL, por qué no se está llamando al constructor de tipos en el siguiente código:

internal struct SomeValType { static SomeValType() { Console.WriteLine("This never gets displayed"); } public Int32 _x; } public sealed class Program { static void Main(string[] args) { SomeValType[] a = new SomeValType[10]; a[0]._x = 123; Console.WriteLine(a[0]._x); //Displays 123 } }

Entonces, aplicando las siguientes reglas para los constructores de tipos, simplemente no puedo ver por qué el constructor de tipo de valor anterior no se llama en absoluto.

  1. Puedo definir un constructor de tipo de valor estático para establecer el estado inicial del tipo.
  2. Un tipo no puede tener más de un constructor, no hay uno predeterminado.
  3. Los constructores de tipo son implícitamente privados
  4. El compilador JIT comprueba si el constructor de tipo del tipo ya se ha ejecutado en este dominio de aplicación. Si no, emite la llamada al código nativo, de lo contrario no lo hace, ya que sabe que el tipo ya está ''inicializado''.

Entonces ... No puedo entender por qué no puedo ver que se esté construyendo este tipo de matriz.

Mi mejor suposición sería que podría ser:

  1. La forma en que CLR construye una matriz de tipo. Hubiera pensado que se llamaría al constructor estático cuando se creó el primer elemento
  2. El código en el constructor no está inicializando ningún campo estático por lo que se ignora. He experimentado con la inicialización de campos estáticos privados dentro del constructor, pero el campo sigue siendo el valor 0 predeterminado, por lo que no se llama al constructor.
  3. O ... el compilador de alguna manera está optimizando la llamada de constructor debido a que se está configurando el Int32 público, ¡pero eso es una suposición confusa en el mejor de los casos!

Las mejores prácticas, etc. asside, estoy súper intrigado porque quiero poder ver por mí mismo por qué no se llama.

EDITAR: Agregué una respuesta a mi propia pregunta a continuación, solo una cita de lo que dice Jeffrey Richter al respecto.

Si alguien tiene alguna idea, entonces sería genial. Muchas gracias, James


De §18.3.10 del estándar (ver también El libro de lenguaje de programación C # ):

La ejecución de un constructor estático para una estructura se desencadena al ocurrir el primero de los siguientes eventos dentro de un dominio de aplicación:

  • Se hace referencia a un miembro de instancia de la estructura.
  • Se hace referencia a un miembro estático de la estructura.
  • Se llama a un constructor explícitamente declarado de la estructura.

[ Nota : La creación de valores por defecto (§18.3.4) de tipos de estructuras no activa el constructor estático. (Un ejemplo de esto es el valor inicial de los elementos en una matriz.) Nota final ]

Por lo tanto, estoy de acuerdo con usted en que las dos últimas líneas de su programa deben activar la primera regla.

Después de las pruebas, el consenso parece ser que desencadena constantemente métodos, propiedades, eventos e indizadores. Eso significa que es correcto para todos los miembros de instancia explícitos, excepto los campos. Entonces, si las reglas C # 4 de Microsoft fueron elegidas para el estándar, eso haría que su implementación pasara de mayormente correcta a mayormente incorrecta.


Este es el comportamiento loco por diseño del atributo "beforefieldinit" en MSIL. También afecta a C ++ / CLI, presenté un informe de error donde Microsoft explicó muy bien por qué el comportamiento es así y señalé varias secciones en el estándar de idioma que no estaban de acuerdo / necesitan actualizarse para describir el comportamiento real. . Pero no es públicamente visible. De todos modos, aquí está la última palabra sobre esto de Microsoft (discutiendo una situación similar en C ++ / CLI):

Dado que estamos invocando el estándar aquí, la línea de la Partición I, 8.9.5 dice esto:

Si se marca BeforeFieldInit, entonces el método de inicialización del tipo se ejecuta en, o en algún momento antes, el primer acceso a cualquier campo estático definido para ese tipo.

Esa sección en realidad detalla cómo una implementación del lenguaje puede elegir prevenir el comportamiento que está describiendo. C ++ / CLI elige no hacerlo, sino que le permiten al programador hacerlo si lo desean.

Básicamente, dado que el siguiente código no tiene campos estáticos, el JIT es completamente correcto al simplemente no invocar constructores de clases estáticas.

El mismo comportamiento es lo que estás viendo, aunque en un idioma diferente.


La especificación Microsoft C # 4 ha cambiado ligeramente con respecto a las versiones anteriores y ahora refleja de forma más precisa el comportamiento que estamos viendo aquí:

11.3.10 Constructores estáticos

Los constructores estáticos para estructuras siguen la mayoría de las mismas reglas que para las clases. La ejecución de un constructor estático para un tipo de estructura se desencadena cuando se produce el primero de los siguientes eventos dentro de un dominio de aplicación:

  • Se hace referencia a un miembro estático del tipo struct.
  • Se llama a un constructor explícitamente declarado del tipo de estructura.

La creación de valores predeterminados (§11.3.4) de tipos de estructuras no activa el constructor estático. (Un ejemplo de esto es el valor inicial de los elementos en una matriz).

Las especificaciones de ECMA y Microsoft C # 3 tienen un evento adicional en la lista: "Se hace referencia a un miembro de instancia del tipo de estructura". Entonces parece que C # 3 estaba en contravención de sus propias especificaciones aquí. La especificación C # 4 se ha alineado más estrechamente con el comportamiento real de C # 3 y 4.

EDITAR...

Después de una investigación más exhaustiva, parece que prácticamente todos los accesos a miembros de instancia, excepto el acceso directo al campo, activarán el constructor estático (al menos en las implementaciones actuales de C # 3 y 4 de Microsoft).

Entonces las implementaciones actuales están más estrechamente correlacionadas con las reglas dadas en las especificaciones ECMA y C # 3 que aquellas en la especificación C # 4: las reglas C # 3 se implementan correctamente cuando se accede a todos los miembros de la instancia, excepto los campos; las reglas de C # 4 solo se implementan correctamente para el acceso de campo.

(Las diferentes especificaciones están todas de acuerdo, y aparentemente implementadas correctamente, cuando se trata de las reglas relacionadas con el acceso de miembros estáticos y los constructores declarados explícitamente).


Otra muestra interesante:

struct S { public int x; static S() { Console.WriteLine("static S()"); } public void f() { } } static void Main() { new S().f(); }


Simplemente presenté esto como una ''respuesta'' para poder compartir lo que el propio Sr. Richter escribió al respecto (si alguien tiene un enlace para la última especificación de CLR por cierto, es fácil obtener la edición de 2006, pero le resulta un poco más difícil obtener la última)

Para este tipo de cosas, generalmente es mejor mirar la especificación CLR que la especificación C #. La especificación de CLR dice:

4. Si no está marcado BeforeFieldInit, entonces el método de inicialización de ese tipo se ejecuta en (es decir, se desencadena):

• primer acceso a cualquier campo estático de ese tipo, o

• primera invocación de cualquier método estático de ese tipo o

• primera invocación de cualquier instancia o método virtual de ese tipo si es un tipo de valor o

• primera invocación de cualquier constructor para ese tipo.

Como ninguna de esas condiciones se cumple, el constructor estático no se invoca. Las únicas partes difíciles de notar son que "_x" es un campo de instancia, no un campo estático, y la construcción de una matriz de estructuras no invoca ningún constructor de instancia en los elementos de la matriz.


Supongo que estás creando un ARRAY de tu tipo de valor. Entonces la nueva palabra clave se usaría para inicializar la memoria de la matriz.

Es válido decir

SomeValType i; i._x = 5;

sin ninguna palabra clave nueva en ninguna parte, que es esencialmente lo que estás haciendo aquí. Si SomeValType fuera un tipo de referencia, tendría que inicializar cada elemento de su matriz con

array[i] = new SomeRefType();


Actualización: mi observación es que, a menos que se utilice el estado estático, el constructor estático nunca se tocará, algo que el tiempo de ejecución parece decidir y no se aplica a los tipos de referencia. Esto nos lleva a preguntar si es un error porque tiene poco impacto, es por diseño o es un error pendiente.

Actualización 2: personalmente, a menos que esté haciendo algo funky en el constructor, este comportamiento del tiempo de ejecución nunca debería causar un problema. Tan pronto como accede al estado estático, se comporta correctamente.

Actualización3: además de un comentario de LukeH, y al hacer referencia a la respuesta de Matthew Flaschen, implementar y llamar a su propio constructor en la estructura también activa el constructor estático. Lo que significa que en uno de los tres escenarios, el comportamiento no es lo que dice en la lata.

Acabo de agregar una propiedad estática al tipo y accedí a esa propiedad estática: llamó al constructor estático. Sin el acceso de la propiedad estática, simplemente creando una nueva instancia del tipo, no se llamó al constructor estático.

internal struct SomeValType { public static int foo = 0; public int bar; static SomeValType() { Console.WriteLine("This never gets displayed"); } } static class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { // Doesn''t hit static constructor SomeValType v = new SomeValType(); v.bar = 1; // Hits static constructor SomeValType.foo = 3; } }

Una nota en este enlace especifica que los constructores estáticos no se invocan cuando simplemente se accede a las instancias:

http://www.jaggersoft.com/pubs/StructsVsClasses.htm#default