Before we consider examples, however, let’s consider why we need to implement value-based equality (or value equality for short) in the first place. Two references are identical (reference equal) if both refer to the same instance of an object. On the other hand, objects that have the same values are value equal. And, to support value equal, a type needs to implement value-based equality. Two objects that are reference equal obviously are also value equal—the comparison is between two references to the same object. However, two objects can be of equal value if the (relevant) data between them is also equal. Only reference types can be reference equal, thereby supporting the concept of identity—the two references refer to the same instance.
In contrast, value types can never be reference equal. If you compare whether 42 is reference equal to 42, the answer is always false. Each value type is stored in a different location (reference) in memory, and even the simple act of passing value types to method for comparison will create additional copies of the value types.
While value types can never be reference equal, a reference type can support value equals. object includes a static method called ReferenceEquals() that checks whether the two arguments are identical—reference equal. In addition, object defines a virtual Equals() method that, by default, relies on ReferenceEquals() for reference types. However, this Equals() method can be customized (on both reference types and value types) so that they implement value equality rather than reference.
In fact, you should always override Equals() on value types because otherwise you would be left with the default implementation that compares only the first field for value equality. This, however, is insufficient. If a value type had three properties, for example, then checking for value equality by comparing only the first one would likely be inadequate. For example, given an Angle object, a comparison of the first property (Degrees) is not adequate. Two Angles are equal only if Degrees, Minutes, and Seconds are equal (and potentially any other properties that the Angle includes). Therefore, it is almost always necessary to provide a custom implementation of value equality on a value type.
Value equality is not limited to value types, however. Value type equality might also make sense on a reference type. string is a great example. Although string is a reference type, the comparison of two strings should be equal if all the letters in the string are identical, even if the two strings are different instances.
In addition, a correct override of Equals() requires a host of additional members to exist and potentially be overridden, as described later in the section “Implementing Value Equality”. These include GetHashCode() and the == and != operators. Before then, however, let’s delve into how to define a value type.