【问题标题】:Expression trees - unnecessary conversion to int32表达式树 - 不必要的 int32 转换
【发布时间】:2013-09-02 12:00:27
【问题描述】:

表达式树在处理字节和短裤时似乎构建了不必要的转换,它们将两边(例如在二进制表达式中)转换为 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

【问题讨论】:

  • 您使用的是哪个版本的 C# 编译器?在这个阶段我唯一的猜测是,int 相等是 .NET 中的一个内置原始操作,它只为int 定义(评估堆栈上不能存在更短的类型),这在某种程度上是一个因素在这里。

标签: c# linq nhibernate expression-trees


【解决方案1】:

回答你的问题:

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

答案隐藏在事实中,C# 类型 shortushortbytesbyte 缺少算术、比较...运算符

提取:4.1.5 Integral types

对于二进制 +、–、*、/、%、&、^、|、==、!=、>、= 和 T类型,其中T是第一个 的intuintlongulong,可以充分代表所有可能 两个操作数的值。然后使用 T 类型的精度,结果的类型是 T(或 bool 关系运算符)。一个操作数是不允许的 使用二元运算符键入 long,另一个为 ulong 类型。

7.9.1 Integer comparison operators 描述了可用的运算符及其操作数

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...必须应用转换。当然,以后如何将这样的“表达式”转换为 SQL 取决于 LINQ 提供程序。

【讨论】:

    【解决方案2】:

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

    如果它导致问题,您可以尝试发现并删除它 - 如下所示,这是 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)
    

    【讨论】:

    • 感谢您的详细回答,但问题是关于为什么会这样。这是 Linq 提供程序中的一个常见问题,没有意义......
    • @MoranB 是的,我不反对。但是,在没有任何规范的情况下,我能给你的唯一答案是“因为它是这样编码的”。尝试通过 Roslyn 运行它实际上会很有趣,看看 Roslyn 是否做同样的事情——实际上,也许是 mcs/gmcs(单声道编译器)。除此之外,我在想“责备Mads”;p
    • @MarcGravell 我从 Roslyn 和 Mono 得到相同的结果(Convert)。
    猜你喜欢
    • 2011-04-09
    • 1970-01-01
    • 2012-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-15
    • 1970-01-01
    相关资源
    最近更新 更多