Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriden equality operator is never called

The methods of this class

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

So far, so good. But neither ceq nor System.Object::Equals(object, object) take a possibly overridden op_Equality or Object.Equals into account.

Why is that? Why do none of the three proposed ways call operator== or an overridden Equals method? Shouldn't System.Object::Equals(object, object) automatically call any overridden Equals method?


EDIT: The class I used for test purposes looks like this:

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();
}

and neither of the three methods below call any of MyClass's overriden members:

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

tester.IsNull(myClass);
tester.EqualsNull(myClass);
tester.EqualsCall(myClass);
like image 242
Thomas Flinkow Avatar asked Mar 28 '18 07:03

Thomas Flinkow


Video Answer


1 Answers

The point of generics is: they aren't "templates". The exact same IL needs to run for all T. This means that since there are no constraints on T in your example, the only operators that are known in the IL are the operators that exist for object, so == means reference equality, the same as (object)x == (object)y.

Polymorphism, however, does work. So your override on object.Equals(object) should work fine. But: it you use object.Equals(x, y) (the static method) - it does an earlier check for null, before it calls your method. It knows that null and "not null" are not semantically equals. If you don't want that: don't use the static object.Equals(x, y).

The static Equals method you are using could be expressed:

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

So: (same reference, or both null), or (both not null, and x.Equals(y))

This implementation avoids issues where unusual implementations of x.Equals(y) could have things like Equals(a, b) // true but Equals(b, a) // false

like image 196
Marc Gravell Avatar answered Oct 01 '22 18:10

Marc Gravell