In an example, my professor has implemented Equals as follows:
public class Person { private string dni; // ... public override bool Equals(object o) { if (o == null) return (this == null); else { return ((o is Person) && (this.dni == (o as Person).dni)); } } }
I have no experience with C#, but as far as I know this
cannot be null inside a member function (at least this is true in C++ and Java, the languages I know) so the if seems weird to me.
Am I right or is there any component in c# I dont know of which makes the test this == null
necesary?
tl;dr, "this" can only be called from a non-static method and we all know that a non-static method is called from some sort of object which cannot be null.
7. == and != The comparison and not equal to operators are allowed with null in Java.
If you think of it from a programming (i.e. pointer reference) point of view then, yes, two references of null have the same pointer value and, since most of the popular languages will fall back to pointer-equality if no custom equality is defined, null does equal null.
In C#, the compiler does not allow you to assign a null value to a variable. So, C# 2.0 provides a special feature to assign a null value to a variable that is known as the Nullable type. The Nullable type allows you to assign a null value to a variable.
I have no experience with C#, but as far as I know this cannot be null inside a member function (at least this is true in C++ and Java, the languages I know)
Let's begin by noting that your statement is false.
In C++, dispatching a method on a null receiver is undefined behavior and undefined behavior means that anything can happen. "Anything" includes the program passing NULL
as this
and continuing as though nothing was wrong. Of course it is somewhat silly to check whether this
is null in C++ because the check can only be true if you already do not know what your program is doing, because its behavior is undefined.
Whether this
can be null in Java I have no idea.
Now to address your question about C#. Let's assume that ==
is not overloaded. We'll come back to this point later.
Your method is written in C#. Suppose it is invoked from a C# program with a null receiver. The C# compiler evaluates whether the receiver could possibly be null; if it could possibly be null then it ensures that it generates code that does a null check before invoking the method. Therefore this check is pointless in that scenario. This is of course the 99.9999% likely scenario.
Suppose it is invoked via Reflection, as in mike z's answer. In that case it is not the C# language that is performing the invocation; rather, someone is deliberately abusing reflection.
Suppose it is invoked from another language. We have a virtual method; if it is invoked from this other language with virtual dispatch then a null check must be performed, because how else could we know what is in the virtual slot? In that scenario it cannot be null.
But suppose it is invoked from another language using non-virtual dispatch. In that case the other language need not implement the C# feature of checking for null. It could just invoke it and pass null.
So there are several ways in which this
could be null
in C#, but they are all very much out of the mainstream. It is therefore very rare for people to write code as your professor has. C# programmers idiomatically suppose that this
is not null
and never check for it.
Now that we've gotten that out of the way, let's criticize that code some more.
public override bool Equals(object o) { if (o == null) return (this == null); else { return ((o is Person) && (this.dni == (o as Person).dni)); } }
First off there is an obvious bug. We presume that this
could be null, ok, let's run with that. What stops this.dni
from throwing null reference exception??? If you're going to assume that this
can be null then at least do so consistently! (At Coverity we refer to this sort of situation as a "forward null defect".)
Next: we are overriding Equals
and then using ==
inside, presumably to mean reference equality. This way lies madness! Now we have a situation where x.Equals(y)
can be true but x==y
can be false! This is horrid. Please don't go there. If you're going to override Equals
then overload ==
at the same time, and implement IEquatable<T>
while you're at it.
(Now, there is a reasonable argument to be made that madness lies in either direction; if ==
is consistent with Equals
with value semantics then personx == persony
can be different than (object)personx == (object)persony
, which seems strange also. The takeaway here is that equality is pretty messed up in C#.)
Moreover: what if ==
is overridden later? Now Equals
is calling an overridden ==
operator, when the author of the code clearly wishes to be doing a reference comparison. This is a recipe for bugs.
My recommendations are (1) write one static method that does the right thing, and (2) use ReferenceEquals
every time there could possibly be any confusion over what kind of equality is meant:
private static bool Equals(Person x, Person y) { if (ReferenceEquals(x, y)) return true; else if (ReferenceEquals(x, null)) return false; else if (ReferenceEquals(y, null)) return false; else return x.dni == y.dni; }
That nicely covers every case. Notice that it is crystal clear to the reader when reference equality semantics are meant. Also note that this code makes it very easy to put breakpoints on each possibility, for debugging purposes. And finally, notice that we take the cheapest possible early out; if the objects are reference equal then we don't have to do the potentially expensive comparison of the fields!
Now the other methods are easy:
public static bool operator ==(Person x, Person y) { return Equals(x, y); } public static bool operator !=(Person x, Person y) { return !Equals(x, y); } public override bool Equals(object y) { return Equals(this, y as Person); } public bool Equals(Person y) { return Equals(this, y); }
Notice how much more elegant and clear my way is than your professor's way. And notice that my way handles a null this
without ever comparing this
to null directly.
Again: all of this illustrates that the compromise position arrived at, in which both value and reference equality are possible and there are four (==
, !=
, object.Equals(object)
and IEquatable<T>.Equals(T)
) ways to implement equality, is very complicated and confusing even without assuming that this
can or cannot be null
.
If this subject interests you, I describe a slightly harder problem on my blog this week: how to implement comparisons in general, including inequalities.
http://ericlippert.com/2013/10/07/math-from-scratch-part-six-comparisons/
The comments are particularly interesting as a critique of how C# handles equality.
Finally: don't forget to override GetHashCode
. Make sure you do it right.
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