Деревья выражений - ненужное преобразование в int32

Деревья выражений, похоже, создают ненужное преобразование при работе с байтами и шортами, они конвертируют обе стороны (в двоичных выражениях, например) в int32.

Это проблема некоторых поставщиков Linq, которые я видел, каждый из которых должен очистить этот избыточный слой, чтобы перейти к исходному выражению. (NHibernate не удаляет этот слой и создает ужасный CAST в SQL-запросе).

// no conversion
Console.WriteLine((Expression<Func<int, int, bool>>) ((s, s1) => s == s1));
// converts to int32
Console.WriteLine((Expression<Func<short, short, bool>>) ((s, s1) => s == s1));
// converts to int32
Console.WriteLine((Expression<Func<byte, byte, bool>>) ((s, s1) => s == s1));

Если вы попытаетесь создать выражение, которое делает это точное сравнение (без преобразования), вы добьетесь успеха.

Итак, вопрос в том, в чем причина такого поведения?

ИЗМЕНИТЬ .net 4.0 64bit, то же самое относится к 4,5 64-битным

Ответ 1

Это действительно интересно; К сожалению, правила компилятора выражения-дерева формально не заданы - в спецификации есть краткое описание "находятся в другом месте", но: на самом деле это не так.

Если это вызывает проблему, вы можете попытаться ее обнаружить и удалить - что-то вроде ниже, что на 100% не проверено, а также на риск "по собственному усмотрению" и т.д.:

static void Main()
{
    Console.WriteLine(((Expression<Func<short, short, bool>>)((s, s1) => s == s1)).Unmunge());
    Console.WriteLine(((Expression<Func<byte, byte, bool>>)((s, s1) => s == s1)).Unmunge());  
}
static Expression<T> Unmunge<T>(this Expression<T> expression)
{
    return (Expression<T>)RedundantConversionVisitor.Default.Visit(expression);
}
class RedundantConversionVisitor : ExpressionVisitor
{
    private RedundantConversionVisitor() { }
    public static readonly RedundantConversionVisitor Default = new RedundantConversionVisitor();
    protected override Expression VisitBinary(BinaryExpression node)
    {
        if(node.Type == typeof(bool) && node.Method == null
            && node.Left.NodeType == ExpressionType.Convert && node.Right.NodeType == ExpressionType.Convert
            && node.Left.Type == node.Right.Type)
        {
            UnaryExpression lhs = (UnaryExpression)node.Left, rhs = (UnaryExpression)node.Right;
            if (lhs.Method == null && rhs.Method == null && lhs.Operand.Type == rhs.Operand.Type)
            {
                // work directly on the inner values
                return Expression.MakeBinary(node.NodeType, lhs.Operand, rhs.Operand, node.IsLiftedToNull, node.Method);
            }
        }
        return base.VisitBinary(node);
    }
}

Вывод перед:

(s, s1) => (Convert(s) == Convert(s1))
(s, s1) => (Convert(s) == Convert(s1))

Выход после:

(s, s1) => (s == s1)
(s, s1) => (s == s1)

Ответ 2

Чтобы ответить на ваш вопрос:

Почему Деревья выражений, похоже, создают ненужное преобразование при работе с байтами и шортами... Итак, вопрос в том, в чем причина такого поведения?

Ответ скрыт в том, что , что типы С# short, ushort, byte и sbyte не имеют операторов арифметики, сравнения...:

Выдержка: 4.1.5 Интегральные типы

Для двоичных чисел +, -, *,/,%, &, ^, |, ==,! =, > , <, >= и <= операторы, операнды преобразуются в тип T, где T является первым int, uint, long и ulong, которые могут полностью представлять все возможные значения обоих операндов. Затем операцию выполняют с использованием точность типа T, а тип результата - T (или bool для реляционные операторы). Не допускается, чтобы один операнд тип long, а другой - типа ulong с бинарными операторами.

7.9.1 Операторы сравнения целых чисел описывают доступные операторы и их операнды

bool operator ==(int x, int y);
bool operator ==(uint x, uint y);
bool operator ==(long x, long y);
bool operator ==(ulong x, ulong y);
... // other operators, only for int, uint, long, ulong

Преобразование выполняется для вас компилятором (причина, по которой вам удается построить это без явного преобразования)

Поскольку нет операторов, работающих с короткими... необходимо применить преобразование. И, конечно же, это позже зависит от поставщика LINQ, как преобразовать такое выражение в SQL.