重载的等于操作符从未被调用

9
这个类的方法
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);
    }
}
把代码编译成这个IL代码:
.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
@Neijwiert 你说得没错,但我不想改变任何东西,我只是想知道为什么重写的 operator==Equals 方法没有被 o is nullo == nullEquals(o, null) 中的任何一个调用。 - Thomas Flinkow
你为什么认为object.Equals(a, b)不会调用a的重写方法?-查看public static bool Equals(Object objA, Object objB)的源代码,它应该会调用。https://referencesource.microsoft.com/#mscorlib/system/object.cs,f2a579c50b414717,references - Rand Random
@RandRandom 谢谢您的评论。请现在查看我的编辑。 - Thomas Flinkow
@Mardukar 不,这不是复制和粘贴错误。尽管 MyClass 没有显式继承任何类,但它通过设计继承了 Object,因此我可以重写运算符和 Equals 方法。 - Thomas Flinkow
显示剩余2条评论
1个回答

7
泛型的重点在于:它们不是“模板”。所有的T都需要运行相同的中间语言(IL)。这意味着,由于在您的示例中没有对T进行任何约束,因此在IL中已知的操作符仅限于object所存在的操作符,因此==表示引用相等性,与(object)x == (object)y相同。
然而,多态性确实起作用。因此,您在object.Equals(object)上的override应该正常工作。但是: 如果您使用静态方法object.Equals(x, y),则它会在调用您的方法之前进行一个更早的null检查。它知道null和“非空”不是语义相等的。如果您不想要这个功能:请不要使用静态的object.Equals(x, y)
您正在使用的静态Equals方法可以表达为:
public static bool Equals(object objA, object objB) => 
    ((objA == objB) || (((objA != null) && (objB != null)) && objA.Equals(objB)));

因此:(相同引用或两者都为Null),或者(两者都不为Null,并且x.Equals(y)为真)。
这种实现避免了 x.Equals(y) 实现不寻常的问题,其中可能存在Equals(a,b)// true但是Equals(b,a) // false的情况。

谢谢您提到之前的检查,这真的解释了我为什么会遇到这种行为。 - Thomas Flinkow
1
@Sefe 多态性应该始终有效;如果方法是可调用的(即使在使用带有对T类型约束的泛型时),则应调用最具体的override(如果你明白我的意思)。如果没有:恭喜,您破坏了类型系统并发现了CLR错误 :) - Marc Gravell
1
多态的方法应该可以工作,当然。但我的问题是:运算符重载是否具有多态性?与方法不同,在这种通用情况下它们并没有多态性。但在我描述的其他情况下呢? - Sefe
1
@Sefe 运算符重载永远不会是多态的;编辑:略微有例外:dynamic 的行为方式类似于多态,但这并不是真正的多态,而更像是每种类型的运行时解析。 - Marc Gravell
整个问题可以归结为一个要点:“运算符重载不是多态的”。它们相当于new,而不是override - Sefe
显示剩余2条评论

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