Деревья выражений - ненужное преобразование в 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.