[c#] Tal vez un error del compilador de C # en Visual Studio 2015


Answers

En primer lugar, es importante al analizar estos problemas para hacer un reproductor mínimo, de modo que podamos reducir el problema. En el código original hay tres Nullable<T> : el readonly , el static y el Nullable<T> . Ninguno es necesario para reproducir el problema. Aquí hay una reproducción mínima:

struct N<T> {}
struct M { public N<M> E; }
class P { static void Main() { var x = default(M); } }

Esto se compila en la versión actual de VS, pero arroja una excepción de carga de tipo cuando se ejecuta.

  • La excepción no se desencadena por el uso de E Se desencadena por cualquier intento de acceder al tipo M (Como uno esperaría en el caso de una excepción de carga de tipo).
  • La excepción reproduce si el campo es estático o instancia, de solo lectura o no; esto no tiene nada que ver con la naturaleza del campo. (Sin embargo, debe ser un campo! El problema no es repro si es, por ejemplo, un método).
  • La excepción no tiene nada que ver con "invocación"; nada está siendo "invocado" en la reproducción mínima.
  • La excepción no tiene nada que ver con el operador de acceso de miembro ".". No aparece en la reproducción mínima.
  • La excepción no tiene nada que ver con nulables; nada es anulable en la reproducción mínima.

Ahora hagamos algunos experimentos más. ¿Qué pasa si hacemos N y M clases? Te diré los resultados:

  • El comportamiento solo se reproduce cuando ambos son estructuras.

Podríamos continuar para discutir si el problema se reproduce solo cuando M en algún sentido se menciona "directamente" a sí mismo, o si un ciclo "indirecto" también reproduce el error. (Esto último es verdad.) Y como señala Corey en su respuesta, también podríamos preguntarnos "¿los tipos tienen que ser genéricos?" No; hay un reproductor aún más mínimo que este sin genéricos.

Sin embargo, creo que tenemos suficiente para completar nuestra discusión sobre el reproductor y pasar a la cuestión que nos ocupa, que es "¿es un error, y si es así, en qué?"

Claramente, algo está mal, y hoy no tengo tiempo para decidir dónde debe caer la culpa. Aquí hay algunos pensamientos:

  • La regla en contra de las estructuras que contienen miembros de ellos claramente no se aplica aquí. (Consulte la sección 11.3.1 de la especificación C # 5, que es la que tengo presente.) Noto que esta sección podría beneficiarse de una reescritura cuidadosa con los genéricos en mente; algunos de los términos aquí son un tanto imprecisos). E es estático, entonces esa sección no se aplica; si no es estático, los diseños de N<M> y M pueden computarse independientemente.

  • No conozco ninguna otra regla en el lenguaje C # que prohíba esta disposición de tipos.

  • Puede ser que la especificación CLR prohíba esta disposición de tipos, y CLR tiene razón al lanzar una excepción aquí.

Así que ahora vamos a resumir las posibilidades:

  • El CLR tiene un error. Esta topología de tipo debería ser legal, y es incorrecto que la CLR arroje aquí.

  • El comportamiento de CLR es correcto. Esta topología de tipo es ilegal y es correcta de CLR arrojar aquí. (En este escenario, puede darse el caso de que el CLR tenga un error de especificación, ya que este hecho puede no estar adecuadamente explicado en la especificación. No tengo tiempo para realizar el buceo de especificación de CLR hoy).

Supongamos por el argumento que el segundo es verdadero. ¿Qué podemos decir ahora sobre C #? Algunas posibilidades:

  • La especificación del lenguaje C # prohíbe este programa, pero la implementación lo permite. La implementación tiene un error. (Creo que este escenario es falso.)

  • La especificación del lenguaje C # no prohíbe este programa, pero podría hacerse a un costo de implementación razonable. En este escenario, la especificación de C # es defectuosa, debe corregirse y la implementación debe corregirse para que coincida.

  • La especificación del lenguaje C # no prohíbe el programa, pero la detección del problema en el momento de la compilación no se puede realizar a un costo razonable. Este es el caso con casi cualquier falla de tiempo de ejecución; su programa se bloqueó en tiempo de ejecución porque el compilador no pudo evitar que escriba un programa con errores. Este es solo un programa más con errores; desafortunadamente, no tenías razón para saber que tenía errores.

En resumen, nuestras posibilidades son:

  • El CLR tiene un error
  • La especificación C # tiene un error
  • La implementación de C # tiene un error
  • El programa tiene un error

Uno de estos cuatro debe ser verdadero. No sé cuál es. Si me pidieran que adivinara, elegiría el primero; No veo ninguna razón por la cual el cargador de tipo CLR deba resistirse a este. Pero tal vez haya una buena razón por la que no sé; con suerte, un experto en la semántica de carga de tipo CLR sonará.

ACTUALIZAR:

Este problema se rastrea aquí:

https://github.com/dotnet/roslyn/issues/10126

Para resumir las conclusiones del equipo C # en ese tema:

  • El programa es legal según las especificaciones CLI y C #.
  • El compilador C # 6 permite el programa, pero algunas implementaciones de la CLI arrojan una excepción de carga de tipo. Este es un error en esas implementaciones.
  • El equipo de CLR conoce el error y aparentemente es difícil solucionarlo en las implementaciones con errores.
  • El equipo de C # está considerando hacer que el código legal produzca una advertencia, ya que fallará en el tiempo de ejecución en algunas, pero no en todas, las versiones de la CLI.

Los equipos C # y CLR están en esto; sigue con ellos Si tiene alguna otra inquietud sobre este tema, publíquela en el problema de seguimiento, no aquí.

Question

Creo que esto es un error de compilación.

La siguiente aplicación de consola se compila y ejecuta sin problemas cuando se compila con VS 2015:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = MyStruct.Empty;
        }

        public struct MyStruct
        {
            public static readonly MyStruct Empty = new MyStruct();
        }
    }
}

Pero ahora se está poniendo extraño: este código se compila, pero arroja una TypeLoadException cuando se ejecuta.

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = MyStruct.Empty;
        }

        public struct MyStruct
        {
            public static readonly MyStruct? Empty = null;
        }
    }
}

¿Experimenta el mismo problema? Si es así, archivaré un problema en Microsoft.

El código parece sin sentido, pero lo uso para mejorar la legibilidad y para lograr la desambiguación.

Tengo métodos con diferentes sobrecargas como

void DoSomething(MyStruct? arg1, string arg2)

void DoSomething(string arg1, string arg2)

Llamar a un método de esta manera ...

myInstance.DoSomething(null, "Hello world!")

... no compila

Vocación

myInstance.DoSomething(default(MyStruct?), "Hello world!")

o

myInstance.DoSomething((MyStruct?)null, "Hello world!")

funciona, pero se ve feo Lo prefiero de esta manera:

myInstance.DoSomething(MyStruct.Empty, "Hello world!")

Si pongo la variable Empty en otra clase, todo funciona bien:

public static class MyUtility
{
    public static readonly MyStruct? Empty = null;
}

Comportamiento extraño, ¿verdad?

ACTUALIZACIÓN 2016-03-29

Abrí un boleto aquí: http://github.com/dotnet/roslyn/issues/10126

ACTUALIZACIÓN 2016-04-06

Se ha abierto un nuevo ticket aquí: https://github.com/dotnet/coreclr/issues/4049






Links