14

表达式树似乎在处理字节和短裤时构建了不必要的转换,它们将两边(例如在二进制表达式中)转换为 int32。

这是我见过的一些 Linq 提供程序中的一个问题,每个提供程序都必须剥离这个冗余层才能得到原始表达式。(NHibernate 不会删除这一层,并在 SQL 查询中创建一个糟糕的 CAST)。

// 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 64bit

4

2 回答 2

7

要回答您的问题:

为什么表达式树在处理字节和短裤时似乎建立了不必要的转换......所以问题是,这种行为的原因是什么?

答案隐藏在C# 类型short,ushort和缺少算术、比较...运算符bytesbyte事实中:

摘录:4.1.5 积分类型

对于二进制 +、–、*、/、%、&、^、|、==、!=、>、<、>= 和 <= 运算符,操作数转换为 type T,其中T第一个是int, uint, long, 并且ulong可以完全表示两个操作数的所有可能值。然后使用 type 的精度执行操作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

转换是由编译器为您完成的(您在没有显式转换的情况下成功构建它的原因)

因为没有运算符使用 short... 必须应用转换。当然,稍后取决于 LINQ 提供程序,如何将这样的“表达式”转换为 SQL。

于 2013-09-03T05:04:36.930 回答
6

这真的很有趣;不幸的是,表达式树编译器的规则没有正式指定 - 规范中有一个简短的“在其他地方”,但是:它们不是真的。

如果它导致问题,您可以尝试发现并删除它 - 如下所示,这是 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)
于 2013-09-02T12:23:48.557 回答