El código de muestra a continuación ocurrió naturalmente. De repente, mi código fue una FatalExecutionEngineErrorexcepción muy desagradable . Pasé unos 30 minutos tratando de aislar y minimizar la muestra culpable. Compile esto usando Visual Studio 2012 como una aplicación de consola:
class A<T>
{
static A() { }
public A() { string.Format("{0}", string.Empty); }
}
class B
{
static void Main() { new A<object>(); }
}
Debería producir este error en .NET Framework 4 y 4.5:

¿Es este un error conocido, cuál es la causa y qué puedo hacer para mitigarlo? Mi trabajo actual es no usar string.Empty, pero ¿estoy ladrando el árbol equivocado? Cambiar cualquier cosa sobre ese código hace que funcione como cabría esperar, por ejemplo, eliminar el constructor estático vacío de A, o cambiar el parámetro de tipo de objecta int.
Probé este código en mi computadora portátil y no se quejó. Sin embargo, probé mi aplicación principal y también se estrelló en la computadora portátil. Debo haber destrozado algo al reducir el problema, veré si puedo descubrir qué fue eso.
Mi computadora portátil se bloqueó con el mismo código que el anterior, con Framework 4.0, pero el principal se bloquea incluso con 4.5. Ambos sistemas están utilizando VS'12 con las últimas actualizaciones (¿julio?).
Más información :
- Código IL (depuración compilada / Cualquier CPU / 4.0 / VS2010 (¿no debería importar el IDE?)): Http://codepad.org/boZDd98E
- No visto VS 2010 con 4.0. No se bloquea con / sin optimizaciones, CPU de destino diferente, depurador adjunto / no conectado, etc. - Tim Medora
- Se bloquea en 2010 si uso AnyCPU, está bien en x86. Se bloquea en Visual Studio 2010 SP1, utilizando Platform Target = AnyCPU, pero está bien con Platform Target = x86. Esta máquina también tiene VS2012RC instalado, por lo que 4.5 posiblemente realice un reemplazo en el lugar. Use AnyCPU y TargetPlatform = 3.5, entonces no se bloquea, por lo que parece una regresión en el Framework .
- No se puede reproducir en x86, x64 o AnyCPU en VS2010 con 4.0. - Fuji
- Solo sucede para x64, (2012rc, Fx4.5) - Henk Holterman
- VS2012 RC en Win8 RP. Inicialmente No veo este MDA cuando se dirige a .NET 4.5. Cuando se cambió a apuntar a .NET 4.0, apareció la MDA. Luego, después de volver a .NET 4.5, el MDA permanece. - Wayne

Respuestas:
Esta tampoco es una respuesta completa, pero tengo algunas ideas.Creo que he encontrado una explicación tan buena como la que encontraremos sin que alguien del equipo de .NET JIT responda.
ACTUALIZAR
Miré un poco más profundo y creo que he encontrado la fuente del problema. Parece ser causado por una combinación de un error en la lógica de inicialización de tipo JIT y un cambio en el compilador de C # que se basa en la suposición de que el JIT funciona según lo previsto. Creo que el error JIT existía en .NET 4.0, pero fue descubierto por el cambio en el compilador para .NET 4.5.
No creo que
beforefieldinitsea el único problema aquí. Creo que es más simple que eso.El tipo
System.Stringen mscorlib.dll de .NET 4.0 contiene un constructor estático:En la versión .NET 4.5 de mscorlib.dll,
String.cctor(el constructor estático) está notablemente ausente:En ambas versiones, el
Stringtipo está adornado conbeforefieldinit:Traté de crear un tipo que compilara a IL de manera similar (para que tenga campos estáticos pero sin constructor estático
.cctor), pero no pude hacerlo. Todos estos tipos tienen un.cctormétodo en IL:Supongo que dos cosas cambiaron entre .NET 4.0 y 4.5:
Primero: el EE se cambió para que se inicializara automáticamente
String.Emptydesde el código no administrado. Este cambio probablemente se realizó para .NET 4.0.Segundo: el compilador cambió para que no emitiera un constructor estático para la cadena, sabiendo que
String.Emptyse asignaría desde el lado no administrado. Este cambio parece haberse realizado para .NET 4.5.Parece que EE no asigna lo
String.Emptysuficientemente pronto a lo largo de algunas rutas de optimización. El cambio realizado en el compilador (o lo que sea que haya cambiado para hacerString.cctordesaparecer) esperaba que EE realizara esta asignación antes de que se ejecute cualquier código de usuario, pero parece que EE no realiza esta asignación antes de queString.Emptyse use en métodos de clases genéricas reificadas de tipo de referencia.Por último, creo que el error es indicativo de un problema más profundo en la lógica de inicialización de tipo JIT. Parece que el cambio en el compilador es un caso especial para
System.String, pero dudo que el JIT haya hecho un caso especial aquí paraSystem.String.Original
En primer lugar, WOW La gente de BCL se ha vuelto muy creativa con algunas optimizaciones de rendimiento. Muchos de los
Stringmétodos ahora se realizan utilizando unStringBuilderobjeto en caché estático Thread .Seguí esa pista por un tiempo, pero
StringBuilderno se usa en laTrimruta del código, así que decidí que no podría ser un problema estático de Thread.Sin embargo, creo que encontré una extraña manifestación del mismo error.
Este código falla con una infracción de acceso:
Sin embargo, si usted elimine el comentario
//new A<int>(out s);deMainentonces el código funciona bien. De hecho, siAse reifica con cualquier tipo de referencia, el programa falla, pero siAse reifica con cualquier tipo de valor, entonces el código no falla. Además, si comentaAel constructor estático, el código nunca falla. Después de profundizar enTrimyFormat, está claro que el problema es queLengthse está alineando, y que en estas muestras anteriores, elStringtipo no se ha inicializado. En particular, dentro del cuerpo delAconstructor,string.Emptyno está asignado correctamente, aunque dentro del cuerpo deMain,string.Emptyestá asignado correctamente.Es sorprendente para mí que la inicialización de tipo de
Stringalguna manera dependa de siAse reifica o no con un tipo de valor. Mi única teoría es que hay una ruta de código de optimización JIT para la inicialización de tipo genérica que se comparte entre todos los tipos, y que esa ruta hace suposiciones sobre los tipos de referencia BCL ("¿tipos especiales?") Y su estado. Un vistazo rápido a otras clases de BCL conpublic staticcampos muestra que básicamente todas ellas implementan un constructor estático (incluso aquellos con constructores vacíos y sin datos, comoSystem.DBNullySystem.Empty. Los tipos de valores BCL conpublic staticcampos no parecen implementar un constructor estático (System.IntPtrpor ejemplo) Esto parece indicar que el JIT hace algunas suposiciones sobre la inicialización del tipo de referencia BCL.FYI Aquí está el código JITed para las dos versiones:
A<object>.ctor(out string):A<int32>.ctor(out string):El resto del código (
Main) es idéntico entre las dos versiones.EDITAR
Además, el IL de las dos versiones es idéntico, excepto por la llamada a
A.ctorinB.Main(), donde el IL de la primera versión contiene:versus
en el segundo.
Otra cosa a tener en cuenta es que el código JITed para
A<int>.ctor(out string): es el mismo que en la versión no genérica.fuente
string.Emptyo""... :)typeof(string).GetField("Empty").SetValue(null, "Hello world!"); Console.WriteLine(string.Empty);da resultados diferentes en .NET 4.0 frente a .NET 4.5. ¿Está este cambio relacionado con el cambio descrito anteriormente? ¿Cómo puede .NET 4.5 técnicamente ignorarme cambiando un valor de campo? ¿Tal vez debería hacer una nueva pregunta sobre esto?Sospecho firmemente que esto es causado por esta optimización (relacionada con
BeforeFieldInit) en .NET 4.0.Si recuerdo correctamente:
Cuando declara un constructor estático explícitamente,
beforefieldinitse emite, indicando al tiempo de ejecución que el constructor estático debe ejecutarse antes de que cualquier miembro estático acceda .Mi conjetura:
Supongo que de alguna manera arruinaron este hecho en el x64 JITer, de modo que cuando se accede a un miembro estático de un tipo diferente desde una clase cuyo propio constructor estático ya se ha ejecutado, de alguna manera omite la ejecución (o se ejecuta en el orden incorrecto) constructor estático y, por lo tanto, provoca un bloqueo. (No obtiene una excepción de puntero nulo, probablemente porque no está inicializado por nulo).
He no ejecutar el código, por lo que esta parte puede estar equivocado - pero si tuviera que hacer otra conjetura, diría que podría ser algo
string.Format(oConsole.WriteLine, lo que es similar) necesita acceso interno que está causando el accidente, tales como tal vez una clase relacionada con el entorno local que necesita una construcción estática explícita.Nuevamente, no lo he probado, pero es mi mejor conjetura de los datos.
Siéntase libre de probar mi hipótesis y hágame saber cómo va.
fuente
Bno tiene un constructor estático, y no ocurre cuandoAse reifica con un tipo de valor. Creo que es un poco más complicado.BTener un constructor estático no importa mucho. ComoAtiene un ctor estático, el tiempo de ejecución desordena el orden en que se ejecuta cuando se compara con alguna clase relacionada con la configuración regional en algún otro espacio de nombres. Entonces ese campo aún no se ha inicializado. Sin embargo, si creaAuna instancia con un tipo de valor, entonces podría ser el segundo paso del tiempo de ejecución a través de la creación de instanciasA(es probable que el CLR ya lo haya instanciado previamente con un tipo de referencia, como una optimización) para que el orden funcione cuando se ejecuta por segunda vez .beforefieldinitoptimización dada es la causa raíz. Puede ser que parte de la explicación real sea diferente de lo que mencioné, pero la causa raíz es probablemente la misma.A<object>.ctor().Una observación, pero DotPeek muestra la cadena descompilada. Vacía así:
Si declaro el mío de
Emptyla misma manera, excepto sin el atributo, ya no obtengo el MDA:fuente
""resuelve.