Подтвердить что ты не робот

Почему общие и не общие структуры обрабатываются по-разному при построении выражения, которое поднимает оператор == до нуля?

Это выглядит как ошибка при удалении к нулю операндов на общих структурах.

Рассмотрим следующую фиктивную структуру, которая переопределяет 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 для двух значений.

  • Третье выражение проверяет аргумент nullable, чтобы увидеть, имеет ли он значение - если нет, возвращает false. В противном случае вызовы op_Equality.

Пока все хорошо.

Следующий шаг: выполните то же самое с общим типом - измените MyStruct на MyStruct<T> всюду в определении типа и измените его на MyStruct<int> в выражениях.

Теперь третье выражение компилируется, но генерирует исключение времени выполнения InvalidOperationException со следующим сообщением:

Операнды для оператора "Равно" не соответствуют параметрам метода "op_Equality".

Я ожидал бы, что общие структуры будут вести себя точно так же, как и не общие, со всеми описанными выше обнулениями.

Итак, мои вопросы:

  • Почему существует различие между родовыми и не-генерическими структурами?
  • В чем смысл этого исключения?
  • Является ли это ошибкой в ​​С#/. NET?

Полный код для воспроизведения этого доступен в этом стиле.

4b9b3361

Ответ 1

Короткий ответ: да, это ошибка. Я поставил минимальный репродукт и короткий анализ ниже.

Мои извинения. Я написал много этого кода, и поэтому, вероятно, это было плохо.

Я отправил рецензию в команды разработки, тестирования и управления программой Roslyn. Я сомневаюсь, что это будет воспроизведено в Roslyn, но они подтвердят, что это не так, и решить, делает ли это бар для пакета обновления С# 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 верен. Этот логический элемент определяет, является ли результат поднятого равенства поднятым булевым, а в С# его нет, в VB это.)

Ответ 2

Да, эта ошибка исчезла в Roslyn (разрабатываемый компилятор). Мы увидим существующий продукт.