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 present the equality of reference and value types independently.
Comparing Reference Types for Equality
You might be surprised to learn that System. Object 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 foul’ 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 . _ ~. .o something, 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 (==)
It is best to think of the comparison operator as an intermediate option between strict value comparison and strict reference comparison. In most cases, writing the following means that you are comparing references:
bool b = (x == y); II x, y object 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 be converted 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. Equal s (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:
bool b = ReferenceEquals(v,v); II v is a variable of some value type
The reason for this is that 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 because it doesn’t 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.