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);
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With