Comparing Objects for Equality
After discussing operators and briefly touching on
the equality operator, it is worth considering for a moment what
equality means when dealing with instances of classes and structs.
Understanding the mechanics of object equality is essential for
programming logical expressions and is important when implementing
operator overloads and casts, which is the topic of the rest of
this chapter.
The mechanisms of object equality are different
depending on whether you are comparing reference types (instances
of classes) or value types (the primitive data types, instances of
structs or enums). The following sections look at the equality of
reference and value types independently.
Comparing Reference Types for Equality
One aspect of System.Object that can look surprising at first
sight is the fact that it defines three different methods for
comparing objects for equality: ReferenceEquals() and two versions of Equals(). Add to this the comparison operator
(==), and you actually have four ways of
comparing for equality. Some subtle differences exist between the
different methods, which are examined next.
The ReferenceEquals() method
ReferenceEquals()is a static method that tests whether two references
refer to the same instance of a class, specifically whether the two
references contain the same address in memory. As a static method, it is not possible to override, so
the System.Object implementation is what
you always have. ReferenceEquals() will
always return true if supplied with two
references that refer to the same object instance, and false otherwise. It does, however, consider
null to be equal to null:
The virtual Equals() method
The System.Object
implementation of the virtual version of Equals() also works by comparing references.
However, because this method is virtual, you can override it in
your own classes in order to compare objects by value. In
particular, if you intend instances of your class to be used as
keys in a dictionary, you will need to override this method to
compare values. Otherwise, depending on how you override
Object.GetHashCode(), the dictionary
class that contains your objects will either not work at all or
will work very inefficiently. One point you should note when
overriding Equals() is that your
override should never throw exceptions. Once again, this is because
doing so could cause problems for dictionary classes and possibly
certain other .NET base classes that internally call this
method.
The static Equals() method
The static version
of Equals() actually does the same thing
as the virtual instance version. The difference is that the static
version takes two parameters and compares them for equality. This
method is able to cope when either of the objects is null, and, therefore, provides an extra safeguard
against throwing exceptions if there is a risk that an object might
be null. The static overload first checks whether the references it has been passed are
null. If they are both null, it returns true
(because null is considered to be equal
to null). If just one of them is
null, it returns false. If both references actually refer to
some-thing, it calls the virtual instance version of Equals(). This means that when you override the
instance version of Equals(), the effect
is as if you were overriding the static version as well.
Comparison operator (==)
The comparison operator can be best seen as
an intermediate option between strict value comparison and strict
reference comparison. In most cases, writing the following means
that you are comparing references:
However, it is accepted that there are some
classes whose meanings are more intuitive if they are treated as
values. In those cases, it is better to override the comparison
operator to perform a value comparison. Overriding operators is
discussed next, but the obvious example of this is the System.String class for which Microsoft has
overridden this operator to compare the contents of the strings
rather than their references.
Comparing Value Types for Equality
When comparing value types for equality, the
same principles hold as for reference types: ReferenceEquals() is used to compare references,
Equals() is intended for value
comparisons, and the comparison operator is viewed as an
intermediate case. However, the big difference is that value types
need to be boxed in order to convert them to references so that
methods can be executed on them. In addition, Microsoft has already
overloaded the instance Equals() method
in the System.ValueType class in order
to test equality appropriate to value types. If you call
sA.Equals(sB) where sA and sB are instances
of some struct, the return value will be true or false, according
to whether sA and sB contain the same values in all their fields. On
the other hand, no overload of == is
available by default for your own structs. Writing (sA == sB) in any expression will result in a
compilation error unless you have provided an overload of
== in your code for the struct in
question.
Another point is that ReferenceEquals()always returns false when applied to value types, because to call
this method, the value types will need to be boxed into objects.
Even if you write the following, you will still get the answer of
false:
The reason for this is because v will be boxed separately when converting each
parameter, which means you get different references. Because of
this, there really is no reason to call ReferenceEquals() to compare value types as it
doesn’t really make much sense.
Although the default override of Equals() supplied by System.ValueType will almost certainly be adequate
for the vast majority of structs that you define, you might want to
override it again for your own structs in order to improve
performance. Also, if a value type contains reference types as
fields, you might want to override Equals() to provide appropriate semantics for these
fields, because the default override of Equals() will simply compare their addresses.
|