Reference Equality Versus Value Equality

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 which 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 of 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.

Calling ReferenceEquals() on value types will always return false.

While value types can never be reference equal, a reference types 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, invokes 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 which only compares 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 first property would likely inadequate. For example, given an Angle object, a comparison of the first property (Degrees) is not adequate. Two Angless are equal only if Degrees, Minutes, Seconds are equal, (and potentially any other properties that the Angle includes). Therefore, it is virtually 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 instance.

In addition, a correct override of Equals() requires a host of additional members to exist and potentially be overridden as described later in the chapter – Implementing Value Equality. These include GetHashCode() and the == and != operators. Before then, however, lets delve into how to define a value type.

The implementation of object.Equals(), the default implementation on all objects before overriding, relies on ReferenceEquals() alone.
All value types override the default implementation to instead compare one or more values within the value type.
Reference types can optionally also override Equals() to support value equality.
It is always recommended to implement equality on custom value types.
{{ snackbarMessage }}