【问题标题】:Overriden equality operator is never called永远不会调用覆盖相等运算符
【发布时间】:2018-03-28 07:46:55
【问题描述】:

这个类的方法

public class NullTester
{
    public bool EqualsNull<T>(T o) where T : class
    {
        return o == null;
    }

    public bool IsNull<T>(T o) where T : class
    {
        return o is null;
    }

    public bool EqualsCall<T>(T o) where T : class
    {
        return object.Equals(o, null);
    }
}

compile into this IL code:

.method public hidebysig 
    instance bool EqualsNull<class T> (
        !!T o
    ) cil managed 
{
    .maxstack 8

    IL_0000: ldarg.1
    IL_0001: box !!T
    IL_0006: ldnull
    IL_0007: ceq                                                          // IMPORTANT
    IL_0009: ret
} // end of method C::EqualsNull

.method public hidebysig 
    instance bool IsNull<class T> (
        !!T o
    ) cil managed 
{
    .maxstack 8

    IL_0000: ldarg.1
    IL_0001: box !!T
    IL_0006: ldnull
    IL_0007: ceq                                                          // IMPORTANT
    IL_0009: ret
} // end of method C::IsNull

.method public hidebysig 
    instance bool EqualsCall<class T> (
        !!T o
    ) cil managed 
{
    .maxstack 8

    IL_0000: ldarg.1
    IL_0001: box !!T
    IL_0006: ldnull
    IL_0007: call bool [mscorlib]System.Object::Equals(object, object)   // IMPORTANT
    IL_000c: ret
} // end of method C::EqualsCall

到目前为止,一切都很好。 ceqSystem.Object::Equals(object, object) 都不会考虑可能被覆盖的op_EqualityObject.Equals

这是为什么呢?为什么三种提议的方法都没有调用operator== 或覆盖的Equals 方法? System.Object::Equals(object, object) 不应该自动调用任何被覆盖的Equals 方法吗?


编辑: 我用于测试目的的类如下所示:

public class MyClass
{
     public static bool operator ==(MyClass m1, MyClass m2) => throw new Exception();

     public static bool operator !=(MyClass m1, MyClass m2) => throw new Exception();

     public override bool Equals(object obj) => throw new Exception();
}

以下三个方法都没有调用MyClass 的任何覆盖成员:

NullTester tester = new NullTester();
MyClass myClass = new MyClass(); 

tester.IsNull(myClass);
tester.EqualsNull(myClass);
tester.EqualsCall(myClass);

【问题讨论】:

  • 在我看来,尝试将对象的结果与 == 运算符进行比较更改为 null 似乎是糟糕的设计。其他将使用您的代码的程序员可能会感到困惑。
  • @Neijwiert 你是对的,但我不想改变任何东西,我只想知道为什么@987654339 中的任何一个都没有调用覆盖的operator==Equals 方法@、o == nullEquals(o, null)
  • 为什么你认为 object.Equals(a, b) 不会调用 a 的覆盖方法? - 查看public static bool Equals(Object objA, Object objB)的源代码应该是referencesource.microsoft.com/#mscorlib/system/…
  • @RandRandom 这可能是我的代码有问题,但Equals 方法中没有调试器断点被命中。另外,我将Equals 方法更改为public override bool Equals(MyClass o, MyClass o2) =&gt; throw new Exception();,它也没有被调用。
  • 你能显示你的代码吗,如果可能的话,mcve 会很好。 stackoverflow.com/help/mcve

标签: c# operator-overloading cil


【解决方案1】:

泛型的要点是:它们不是“模板”。完全相同的 IL 需要为所有 T 运行。这意味着由于在您的示例中对 T 没有任何限制,因此 IL 中唯一已知的运算符是存在于 object 的运算符,因此 == 表示引用相等,与 (object)x == (object)y 相同。

然而,多态性确实起作用。所以你在object.Equals(object) 上的override 应该可以正常工作。但是:如果您使用object.Equals(x, y)(静态方法) - 它更早检查null它调用您的方法之前。它知道 null 和“not null”在语义上并不相等。如果你不想这样:不要使用静态的object.Equals(x, y)

你使用的静态Equals方法可以表示为:

public static bool Equals(object objA, object objB) => 
    ((objA == objB) || (((objA != null) && (objB != null)) && objA.Equals(objB)));

所以:(相同的引用,或两者都为空),或(均不为空,和x.Equals(y)

此实现避免了x.Equals(y) 的异常实现可能出现Equals(a, b) // trueEquals(b, a) // false 之类的问题

【讨论】:

  • 感谢您提及之前的检查,这确实解释了我遇到这种行为的原因。
  • 就泛型而言,我将通过“等式运算符重载不是多态的” 记住这一点。但是没有泛型的情况(比如派生实例作为参数传递给基类参数)呢?那么它们是多态的吗?那么其他运算符的重载呢?
  • @Sefe 多态应该总是工作;如果方法是可调用的(即使在T 上使用具有类型约束的泛型),那么应该调用最具体的override(如果你明白我的意思的话)。如果不是:恭喜,您破坏了类型系统并发现了一个 CLR 错误 :)
  • 嗯,多态方法应该可以工作,当然。但我的问题是:运算符重载是多态的吗?与方法不同,它们不在此通用场景中。但它们是否属于我所描述的其他情况?
  • @Sefe 运算符重载是从不多态的;编辑:轻微警告:dynamic 的行为方式 like 多态性,但这并不是真正的多态性,而是按类型运行时分辨率
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-19
  • 1970-01-01
  • 1970-01-01
  • 2012-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多