如何检查两个 Expression<Func<T, bool>> 是否相同?

53

是否有可能找出两个表达式是否相同?

例如,给出以下四个表达式:

        Expression<Func<int, bool>> a = x => false;
        Expression<Func<int, bool>> b = x => false;
        Expression<Func<int, bool>> c = x => true;
        Expression<Func<int, bool>> d = x => x == 5;

那么,至少我们可以看到:

  • a == b
  • a != c
  • a != d

但是我能做些什么来在我的代码中找出这个问题吗?

翻了一下 msdn 文档库,上面说:

Equals:确定指定的对象是否等于当前的Object。(继承自Object。)

我猜至少 Expression 类没有重写 equals 方法成为 Equatable?那么你会怎么做呢?或者我有点要求过高了?:p


如果涉及到MemberInfo,也就是某个方法、属性或字段,那么你可以先获取该成员信息,然后计算它的哈希值。 - nawfal
3个回答

42
您可以查看在Linq to db4o内部使用的类型ExpressionEqualityComparer。它实现了接口IEqualityComparer<T>,因此可用于泛型集合以及独立使用。
它使用类型ExpressionComparison来比较两个表达式的相等性,以及HashCodeCalculation来从表达式中计算出哈希码。
所有这些都涉及到访问表达式树,因此如果您重复执行它,则可能会非常昂贵,但也可能非常方便。
该代码可在GPL或dOCL下获取。
例如,这是您的测试:
using System;
using System.Linq.Expressions;

using Db4objects.Db4o.Linq.Expressions;

class Test {

    static void Main ()
    {
        Expression<Func<int, bool>> a = x => false;
        Expression<Func<int, bool>> b = x => false;
        Expression<Func<int, bool>> c = x => true;
        Expression<Func<int, bool>> d = x => x == 5;

        Func<Expression, Expression, bool> eq =
            ExpressionEqualityComparer.Instance.Equals;

        Console.WriteLine (eq (a, b));
        Console.WriteLine (eq (a, c));
        Console.WriteLine (eq (a, d));
    }
}

它确实打印True、False、False。


1
你可以浏览实现,它们在同一个文件夹中。 - Jb Evain
我想我会尝试找另一种方法来解决我正在努力弄清楚的问题:p 但是你的答案似乎提供了一个可行的解决方案,所以我会将其标记为已接受=) - Svish
你必须引入大量的代码才能让它工作,比如重新实现 ExpressionVisitor :( - Johnno Nolan
3
@JohnNolan,你需要。对于 .net 3.5,ExpressionVisitor 没有暴露出来,所以你需要自己编写代码。对于 .net 4.0,这段代码需要更新为新的表达式类型。由于它不是内置的,因此需要大量的工作并不令人意外。 - Jb Evain
确实在这个解决方案中投入了巨大的努力。 - Johnno Nolan
显示剩余2条评论

23
作为一个简单的回答,你可以检查ToString() - 它应该至少指出它们明显不同的地方(虽然它会包括变量名,所以变量名必须相同)。
要准确地检查等价性...更难 - 需要在许多不同的节点类型上进行大量工作。

哈哈,现在这可能真的有点可行了... :D - Svish
3
不是每个表达式都有可用的字符串表示形式。例如,Convert并没有指明它要将哪些类型进行转换。 - Jb Evain
3
没错 - 我说过它会找到明显错误的答案,但仅此而已。要彻底完成工作,您需要正确地遍历树形结构,检查实际使用的运算符等。 - Marc Gravell
仍然感谢分享。在一些简单的场景下可能很有用。 - Shimmy Weitzhandler
实际上,这对我在表达式上并没有起作用。我使用了LambdaCompare和Explosuress来完成这个任务。但是这个解决方案对于Func<>的相等性确实有效。 - Erailea

4

我认为这可能很难做到,除非在最简单的情况下。

例如:

var numbers1 = Enumerable.Range(1, 20);
Expression<Func<int, IEnumerable<int>>> a = x => numbers1;
var numbers2 = Enumerable.Range(1, 20);
Expression<Func<int, IEnumerable<int>>> b = x => numbers2;

从技术上讲,这两个表达式是相等的,但如何在不评估每个表达式中返回的IEnuemrable的情况下确定它们是否相等呢?


1
我在这里问大家:p 嘿嘿。但是是的,我能看到问题..但我也..看不到它。因为就像你说的那样,从技术上讲,它们实际上是相等的。而且,至少我认为,一个表达式树应该基于它们的节点和数据类型与另一个表达式树可比较。 - Svish
7
在你的例子中,我认为我可能会考虑这些表达式不相等,因为它们包含对一个不同的对象的引用... - Svish
1
numbers1或numbers2将被序列化为ConstantExpression节点,它们的值不相等。 - Jb Evain
我认为你对问题的表达与原帖作者的表达差不多。 - harpo
4
它们从定义上来说并不相等,因为它们都引用闭包中的不同IEnumerable<int>实例,该闭包会转换为一个ConstantExpression,其中Value属性持有对RangeEnumerable的引用,而这些对象没有以明智的方式实现Equals或GetHashCode。 - George Polevoy
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接