【问题标题】:Expression.Equal solution that honors "Equals" overloads and implicit operators支持“Equals”重载和隐式运算符的 Expression.Equal 解决方案
【发布时间】:2020-01-31 21:21:21
【问题描述】:

在运行时使用表达式树编译代码时,我们可能会发现自己需要检查不确定类型的对象是否相等。

如果我们只是为每种情况手动编写代码,编译器会为我们考虑很多事情。想到的最多的是:

  1. 如果通用重载 T1.Equals<T2> 或 T2.Equals<T1> 可用,则使用它。
  2. 否则,如果任一类型有一个隐式运算符可以让我们应用 (1),则使用该运算符。
  3. 否则,将使用bool Equals(object)(当然要考虑潜在的覆盖)。

很遗憾,Expression.Equal(Expression left, Expression right) 并没有为我们做所有这些事情。

我们如何才能实现编译器通常提供的相同行为?

【问题讨论】:

  • 您必须阅读规范并自己实施所有这些检查/搜索。据我所知,没有什么是公开可见的——dynamic 正是这样做的——你可能想了解它是如何完成的。 (我不确定该代码是否公开可用)
  • 使用表达式树,您只能创建一个方法,为了您的目标,您需要实现整个类。要么使用需要 Know-How 的 TypeBuilder,要么生成 C# 源代码文件并编译,这样更容易但速度稍慢。
  • 从好的方面来说,我意识到通常两种情况之一适用。 (A) 类型是结构体,为了避免装箱,使用Equals(T) 接口发出信号的通用Equals(T) 重载是有好处的。 (B) 类型是一个类。如果有直接的IEquatable<T> 接口,我们可以使用它。如果只有 base 类型在其自身上实现 IEquatable<T>,编译器通常仍会选择它,但我们的实现找不到它。幸运的是,这无关紧要:Equals(object) 应该始终返回与 Equals(T) 相同的结果,用于 T 参数。
  • 我应该注意到我之前的评论部分适用于 我只是比较相同编译时类型的对象。如果不是这种情况,Equals 调用的操作数通常可能会被编译器隐式转换,这会使在运行时执行相同操作的实现变得复杂。

标签: c# expression equals expression-trees overload-resolution


【解决方案1】:

假设我们只比较相同编译时类型的对象,我们可以在构建调用表达式时遵循一个简单的两步过程:

  1. 如果编译时类型实现IEquatable<T>,其中T是类型本身,则调用对应的Equals(T)方法。

这可以防止对实现 IEquatable<T> 的结构进行装箱,正是出于这个原因,他们经常这样做。它还有一个额外的好处,那就是在类上调用最明显的 Equals 重载。

  1. 否则,调用常规(可能被覆盖)Equals(object) 方法。

有两个注意事项。

第一个警告是,在没有 IEquatable<T> 接口的情况下,Equals(T) 方法会被忽略。这可能被认为是合理的:如果开发人员没有添加接口,那么他们是否希望该方法用于此目的并不完全清楚。

第二个警告是,对于类,base 类上的IEquatable<T> 接口被忽略,而编译器可能更喜欢与之对应的重载。幸运的是,我们可以合理地期望 Equals(object) 为 T 对象返回与 Equals(T) 相同的结果。如果不是,开发者提供了一个模棱两可的平等定义,所有的赌注都没有了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-17
    • 1970-01-01
    • 1970-01-01
    • 2010-09-13
    • 2021-12-10
    • 1970-01-01
    相关资源
    最近更新 更多