Linq表达式如何确定相等性?

11

我考虑在字典中使用Linq表达式作为键。但是,我担心会得到奇怪的结果,因为我不知道Linq表达式如何确定相等性。

从Expression派生的类比较值相等还是引用相等?换句话说,

        Expression<Func<object>> first = () => new object(); 
        Expression<Func<object>> second = ()=>new object();
        bool AreTheyEqual = first == second;

4
你尝试过了吗?LinqPad非常适合测试少量代码片段。 - R. Martinho Fernandes
4
这个问题的前提不正确;Dictionary<,> 不使用 == 运算符来比较键的相等性。 - Ani
在这种情况下,它们既没有相同的引用也没有相同的值。 - Rangoric
1
@Rangoric 对我来说它们的值是相同的(即使它们使用引用相等语义):生成的表达式树将具有相同类型和相同值的对象。甚至它们的字符串表示也是相同的。 - R. Martinho Fernandes
3个回答

11

你的测试比较的是表达式。 表达式本身只提供引用相等性;你的测试可能会显示“false”。 要检查语义相等性,你需要做很多工作,例如-是:

x => 123

并且

y => 123

等价吗?作为一个简单的测试,你可以比较ToString(),但这种方法非常脆弱。


不,ParameterExpression的名称可能会有所不同。但我明白你的意思。根据我的理解,我认为我的表达式应该返回true,而你的表达式应该返回false,因为实现可能取决于参数的名称。 - smartcaveman
1
@smartcaveman:如果您不想要像Marc展示的语义相等性,并且希望所有内容都完全相等,包括参数名称等,则可以为比较编写自己的ExpressionVisitor。 - R. Martinho Fernandes
1
@Martinho @smartcaveman - 确实;在4.0版本中,一个完整的访客需要做很多工作。 - Marc Gravell
哇塞。我只是想创建一个函数,如果一个 Expression<Func<T, bool>> 等于另一个,则返回 bool。在表达式树的语法语义上相等。这是为了我的单元测试。我正在检查一些动态表达式构建代码是否正常工作。 - Matt Kocaj
@Marc 我猜对了。我也一直在尝试通过编写测试来规避它,这些测试断言模拟源的匹配元素和相同模拟源的不匹配元素。然后,将生成的表达式应用于源中,并断言来自源的相同匹配和非匹配,并强制要求模拟结果与生成的表达式相同。你认为这样做是合理的,还是有更简单的方法在(a)插入值到预期和实际委托之间或者(b)全力构建访问器以比较相等之间达成妥协? - Matt Kocaj
显示剩余5条评论

0
正如其他人所指出的那样,Expression 的 == 运算符使用默认的“引用相等性”检查 - “它们是否都是堆中同一位置的引用?”。这意味着像您的示例代码这样的代码可能会返回 false,因为您的表达式字面量将被实例化为不同的 Expression 实例,而不考虑任何语义上的相等性。使用 lambda 作为事件处理程序也存在类似的挫折:
MyEvent += (s, a) => DoSomething();
...
MyEvent -= (s, a) => DoSomething(); //<-- will NOT remove the added handler

检查语义相等性是棘手的。在这种特定情况下,您可能能够访问表达式树的所有节点并比较所有字符串、值类型和方法引用,以确定它们是否执行相同的操作。然而,通过检查以下示例中的两个 lambda,它们在语义上是等价的,但您很难编写一个方法来证明它:

   public void MyMethod() {...}
   public void AnotherMethod { MyMethod(); };

   ...

   Action one = () => MyMethod();
   Action two = () => AnotherMethod();

   var equal = one == two; // false

我认为这两种方法不应该被评估为相等。但是,你知道检查方法体的方法吗? - smartcaveman
您可以通过获取有关方法的MethodInfo来反射地获取方法体。还可以使用PartialEvaluator(来自IQToolkit)的方式来检查它;我知道Linq2SQL和一些其他具有Linq提供程序的ORM似乎能够转换用户定义的扩展方法,但我不知道复制它有多容易(可能根本不容易)。 - KeithS

0

比较任何两个不是值类型的对象(包括表达式)使用 == 比较对象引用,因此它们不相等。正如一位评论者所指出的那样,字典将使用 EqualsGetHashCode 来确定相等性,这仍然默认会确定它们不相等。

您可以创建一个继承 System.Linq.Expression 的类,并覆盖 GetHashCodeEquals 以某种方式使用结果,然后将其用作字典的键。


1
你的第一句话是误导性的。类型可以提供静态 == 运算符。 - Marc Gravell
1
此外,还有大量的表达式类;在这里,子类路线不是一个选项。 - Marc Gravell
这样好一些吗?还是你指的是我不理解的东西? - Jamie Treworgy
@jamietre 不是的,甚至一个类也可以定义一个 == 运算符。如果这样做了,编译器将使用定义的运算符而不是 referenceequals。 - Marc Gravell
Marc,我理解你的想法,但我还是不明白为什么你不能以这种方式使用派生类。如果您创建的对象以添加到字典中的派生类型为主,而内部表达式则不是,那么字典只会要求最外层对象的哈希码,对吗? - Jamie Treworgy
显示剩余6条评论

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