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):
La primera expresión que toma solo
MyStruct
(no anulables), simplemente llamaop_Equality
(nuestra implementación deoperator ==
)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 (ambosnull
iguales), devuelvetrue
. Si solo uno tiene un valor, devuelvefalse
. De lo contrario, llama aop_Equality
en los dos valores.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:
- ¿Por qué hay una diferencia entre las estructuras genéricas y no genéricas?
- ¿Cuál es el significado de esta excepción?
- ¿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.