Почему общие и неуниверсальные структуры обрабатываются по-разному при построении выражения, которое поднимает оператор == до нуля?
Это похоже на ошибку при поднятии до нуля операндов на общих структурах.
Рассмотрим следующую фиктивную структуру, которая переопределяет 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; }
}
Теперь рассмотрим следующие выражения:
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;
Все три компилируются и запускаются, как и ожидалось.
Когда они скомпилированы (используя .Compile()
) они производят следующий код (перефразированный на английский из IL):
Первое выражение, которое принимает только
MyStruct
(не обнуляемый) args, просто вызываетop_Equality
(наша реализацияoperator ==
)Второе выражение, когда скомпилировано, производит код, который проверяет каждый аргумент, чтобы увидеть, если он
HasValue
, Если оба не (оба равныnull
), возвращаетtrue
, Если только один имеет значение, возвращаетfalse
, В противном случае звонкиop_Equality
на двух значениях.Третье выражение проверяет обнуляемый аргумент, чтобы увидеть, имеет ли оно значение - если нет, возвращает false. В противном случае звонки
op_Equality
,
Все идет нормально.
Следующий шаг: сделать то же самое с универсальным типом - изменить MyStruct
в MyStruct<T>
везде в определении типа, и измените его на MyStruct<int>
в выражениях.
Теперь третье выражение компилируется, но выдает исключение времени выполнения InvalidOperationException
со следующим сообщением:
Операнды для оператора "Равный" не соответствуют параметрам метода "op_Equality".
Я ожидал бы, что родовые структуры будут вести себя точно так же, как неуниверсальные, со всеми отменяемыми значениями, описанными выше.
Итак, мои вопросы:
- Почему существует разница между общими и неуниверсальными структурами?
- В чем смысл этого исключения?
- Это ошибка в C#/.NET?
Полный код для воспроизведения этого доступен в этой сути.
2 ответа
Краткий ответ: да, это ошибка. Я поместил минимальное воспроизведение и краткий анализ ниже.
Мои извенения. Я написал много этого кода, и, вероятно, это было плохо.
Я отправил репродукцию команде разработчиков, тестировщиков и менеджеров программ Roslyn. Я сомневаюсь, что это воспроизводится в Roslyn, но они проверят, что это не так, и решат, делает ли это планку для пакета обновления C# 5.
Не стесняйтесь также указывать проблему на connect.microsoft.com, если вы хотите, чтобы она также отслеживалась там.
Минимальное воспроизведение:
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;
}
}
Код, сгенерированный в минимальном репро, эквивалентен
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 } );
куда infoof
это поддельный оператор, который получает MethodInfo
для данного метода.
Правильный код будет:
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 } );
Equal
Метод не может иметь дело с одним обнуляемым, одним ненулевым операндом. Это требует, чтобы либо оба обнулялись, либо нет.
(Обратите внимание, что false
верно. Этот логический тип определяет, является ли результат снятого равенства логическим значением; в C# это не так, в VB это.)
Да, эта ошибка исчезла в Roslyn (компилятор в разработке). Посмотрим на существующий продукт.