Expression trees seem to build an unnecessary conversion when working with bytes and shorts, they convert both sides (in binary expressions for instance) to int32.
This is an issue in some Linq providers that I've seen, each has to peel this redundant layer to get to the original expression. (NHibernate doesn't remove this layer and creates an awful CAST in the SQL query).
// 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));
If you try to build an expression that makes this exact comparison (without the conversion), you'll succeed.
So the question is, what is the reason for this behavior?
EDIT .net 4.0 64bit, the same applies to 4.5 64bit
To answer your question:
Why Expression trees seem to build an unnecessary conversion when working with bytes and shorts... So the question is, what is the reason for this behavior?
The answer is hidden in the fact, that C# types short
, ushort
, byte
and sbyte
lack the arithmetic, comparison... operators:
Extract: 4.1.5 Integral types
For the binary +, –, *, /, %, &, ^, |, ==, !=, >, <, >=, and <= operators, the operands are converted to type
T
, whereT
is the first ofint
,uint
,long
, andulong
that can fully represent all possible values of both operands. The operation is then performed using the precision of typeT
, and the type of the result isT
(or bool for the relational operators). It is not permitted for one operand to be of type long and the other to be of type ulong with the binary operators.
The 7.9.1 Integer comparison operators describes available operators and their operands
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
The conversion is done for you by Compiler (the reason why you succeed to build that without explicit conversion)
Because there are no operators working with short... the conversion must be applied. And of course, it later depends on the LINQ provider, how to convert such "expression" into SQL.
That's really interesting; unfortunately, the rules of the expression-tree compiler are not formally specified - there is a brief "are elsewhere" in the specification, but : they aren't really.
If it is causing a problem, you could try to spot and remove it - something like below, which is 100% untested and use-at-own-risk, etc:
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);
}
}
Output before:
(s, s1) => (Convert(s) == Convert(s1))
(s, s1) => (Convert(s) == Convert(s1))
Output after:
(s, s1) => (s == s1)
(s, s1) => (s == s1)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With