¿Por qué las estructuras genéricas y no genéricas se tratan de manera diferente cuando se construye una expresión que eleva operator == a nullable?
Esto parece un error en la elevación a null de operandos en estructuras genéricas.
Considere la siguiente estructura ficticia, que anula 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 considere 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 se ejecutan 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 es nullable) args, simplemente llamaop_Equality
(nuestra implementación deoperator ==
)La segunda expresión, cuando se compila, produce código que comprueba cada argumento para ver si
HasValue
. Si ambos no lo hacen (ambos igualesnull
), devuelvetrue
. Si solo uno tiene un valor, devuelvefalse
. De lo contrario, llamaop_Equality
a los dos valores.La tercera expresión comprueba el argumento nullable para ver si tiene un valor - si no, devuelve false. De lo contrario, llama
op_Equality
.
Hasta ahora todo bien.
Siguiente paso: 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 de tiempo de ejecución InvalidOperationException
con el siguiente mensaje:
Los operandos para el operador 'Equal' no coinciden con los parámetros del método 'op_Equality'.
Esperaría que las estructuras genéricas se comportaran exactamente igual que las no genéricas, con todos los nullable-elevación descrita anteriormente.
Así que mis preguntas son:
- ¿Por qué hay una diferencia entre estructuras genéricas y no genéricas?
- ¿Cuál es el significado de esta excepción?
- ¿Es esto un error en C#/. NET?
El código completo para reproducir esto está disponible en esta síntesis.
2 answers
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, así que probablemente fue mi culpa.
He enviado un repro a los equipos de desarrollo, prueba y gestión de programas de Roslyn. Dudo que esto se reproduzca en Roslyn, pero verificarán que no lo hace y decidirán si esto hace la barra para un service pack de C# 5.
Siéntase libre de ingresar un problema en connect.microsoft.com también si quieres rastreó allí también.
Reproducción mínima:
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 operandos nullable, uno no nullable. Requiere que ambos sean nullables o que ninguno lo sea.
(Tenga en cuenta que el false
es correcto. Este Booleano controla si el resultado de una igualdad elevada es un booleano elevado; en C# no lo es, en VB lo es.)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-28 17:51:36
Sí, este error se ha ido en Roslyn (el compilador en desarrollo). Veremos sobre el producto existente.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-28 22:05:33