sharp que operadores operador new jerarquia condicionales and c# generics nullable compiler-bug

que - operador new c#



¿Por qué las estructuras genéricas y no genéricas se tratan de manera diferente cuando se construye una expresión que hace que el operador== sea anulable? (2)

Esto parece un error al levantar a cero los operandos en estructuras genéricas.

Considere la siguiente estructura ficticia, que anula el operator== :

struct MyStruct { private readonly int _value; public MyStruct(int val) { this._value = val; } public override bool Equals(object obj) { return false; } public override int GetHashCode() { return base.GetHashCode(); } public static bool operator ==(MyStruct a, MyStruct b) { return false; } public static bool operator !=(MyStruct a, MyStruct b) { return false; } }

Ahora considera las siguientes expresiones:

Expression<Func<MyStruct, MyStruct, bool>> exprA = (valueA, valueB) => valueA == valueB; Expression<Func<MyStruct?, MyStruct?, bool>> exprB = (nullableValueA, nullableValueB) => nullableValueA == nullableValueB; Expression<Func<MyStruct?, MyStruct, bool>> exprC = (nullableValueA, valueB) => nullableValueA == valueB;

Los tres compilan y corren como se espera.

Cuando se compilan (usando .Compile() ) producen el siguiente código (parafraseado al inglés de la IL):

  1. La primera expresión que toma solo MyStruct (no anulables), simplemente llama op_Equality (nuestra implementación de operator == )

  2. La segunda expresión, cuando se compila, produce un código que verifica cada argumento para ver si tiene HasValue . Si ambos no lo hacen (ambos null iguales), devuelve true . Si solo uno tiene un valor, devuelve false . De lo contrario, llama a op_Equality en los dos valores.

  3. La tercera expresión comprueba el argumento anulable para ver si tiene un valor; de lo contrario, devuelve falso. De lo contrario, llama op_Equality .

Hasta ahora tan bueno.

Paso siguiente: haga exactamente lo mismo con un tipo genérico: cambie MyStruct a MyStruct<T> en todas partes en la definición del tipo, y cámbielo a MyStruct<int> en las expresiones.

Ahora la tercera expresión compila pero lanza una excepción InvalidOperationException tiempo de ejecución con el siguiente mensaje:

Los operandos para el operador ''Igual'' no coinciden con los parámetros del método ''op_Equality''.

Yo esperaría que las estructuras genéricas se comportaran exactamente igual que las no genéricas, con todo el levantamiento de nulos descrito anteriormente.

Así que mis preguntas son:

  1. ¿Por qué hay una diferencia entre las estructuras genéricas y no genéricas?
  2. ¿Cuál es el significado de esta excepción?
  3. ¿Es este un error en C # / .NET?

El código completo para reproducir esto está disponible en esta lista .


La respuesta corta es: sí, eso es un error. He puesto un repro mínimo y un breve análisis a continuación.

Mis disculpas. Escribí mucho de ese código y probablemente fue mi error.

He enviado un informe a los equipos de desarrollo, pruebas y gestión de programas de Roslyn. Dudo que esto se reproduzca en Roslyn, pero verificarán que no, y decidirán si esto hace que la barra de un paquete de servicio C # 5 sea la correcta.

Siéntase libre de ingresar un problema en connect.microsoft.com también si quiere que sea rastreado allí también.

Repro minimo:

using System; using System.Linq.Expressions; struct S<T> { public static bool operator ==(S<T> a, S<T> b) { return false; } public static bool operator !=(S<T> a, S<T> b) { return false; } } class Program { static void Main() { Expression<Func<S<int>?, S<int>, bool>> x = (a, b) => a == b; } }

El código que se genera en la reproducción mínima es equivalente a

ParameterExpression pa = Expression.Parameter(typeof(S<int>?), "a"); ParameterExpression pb = Expression.Parameter(typeof(S<int>), "b"); Expression.Lambda<Func<S<int>?, S<int>, bool>>( Expression.Equal(pa, pb, false, infoof(S<int>.op_Equality) new ParameterExpression[2] { pa, pb } );

Donde infoof es un operador falso que obtiene un MethodInfo para el método dado.

El código correcto sería:

ParameterExpression pa = Expression.Parameter(typeof(S<int>?), "a"); ParameterExpression pb = Expression.Parameter(typeof(S<int>), "b"); Expression.Lambda<Func<S<int>?, S<int>, bool>>( Expression.Equal(pa, Expression.Convert(pb, typeof(S<int>?), false, infoof(S<int>.op_Equality) new ParameterExpression[2] { pa, pb } );

El método Equal no puede tratar con un operando que acepta valores nulos, uno que no acepta nulos. Requiere que ambos sean anulables o que ninguno sea anulable.

(Tenga en cuenta que lo false es correcto. Este booleano controla si el resultado de una igualdad levantada es un booleano levantado; en C # no lo es, en VB lo es).


Sí, este error desapareció en Roslyn (el compilador en desarrollo). Veremos sobre el producto existente.