Equality Comparison
Value Versus Referential Equality (== or !=)
Value Equality – two values are equivalent in some sense.
Referential Equality – two references refer to exactly the same object.
By default the value types use value equality, reference types use referential equality.
Value Equality
// Simple value equality: int x = 5, y = 5; Console.WriteLine (x == y); // True (by virtue of value equality)
DateTimeOffset
The two DateTimeOffsets struct refer to the same point in time.
// A more elaborate demonstration of value equality: var dt1 = new DateTimeOffset (2010, 1, 1, 1, 1, 1, TimeSpan.FromHours(8)); var dt2 = new DateTimeOffset (2010, 1, 1, 2, 1, 1, TimeSpan.FromHours(9)); Console.WriteLine (dt1 == dt2); // True (same point in time)
Reference Equality
// Referential equality: Foo f1 = new Foo { X = 5 }; Foo f2 = new Foo { X = 5 }; Console.WriteLine (f1 == f2); // False (different objects) //Are equal because they refer to the same object Foo f3 = f1; Console.WriteLine (f1 == f3); // True (same objects)
Equality Uri Class
// Customizing classes to exhibit value equality: Uri uri1 = new Uri ("http://www.linqpad.net"); Uri uri2 = new Uri ("http://www.linqpad.net"); Console.WriteLine (uri1 == uri2); // True
Standard Equality Protocols
1. The == and != operators
2. The virtual Equals method in object
3. The IEquatable<T> interface
== and !=
int x = 5; int y = 5; Console.WriteLine (x == y); // True //Object is reference type thus uses referential equality to compare x and y. //Returns false because they refer to two different boxed objects on the heap. object x = 5; object y = 5; Console.WriteLine (x == y); // False
The virtual Object.Equals method
Object.Equals method is defined in System.Object and available to all types and resolved at runtime.
void Main() { object x = 5; object y = 5; Console.WriteLine (x.Equals (y)); // True Console.WriteLine (AreEqual (x, y)); // True Console.WriteLine (AreEqual (null, null)); // True } // Here's an example of how we can leverage the virtual Equals method: public static bool AreEqual (object obj1, object obj2) { if (obj1 == null) return obj2 == null; return obj1.Equals (obj2); // What we've written is in fact equivalent to the static object.Equals method! }
The static object.Equals method
The static helper method does the work of AreEqual accepting two arguments.
static void Main() { object x = 3, y = 3; Console.WriteLine (object.Equals (x, y)); // True x = null; Console.WriteLine (object.Equals (x, y)); // False y = null; Console.WriteLine (object.Equals (x, y)); // True } // Here's how we can use object.Equals: class Test{ T _value; public void SetValue (T newValue) { if (!object.Equals (newValue, _value)) { _value = newValue; OnValueChanged(); } } protected virtual void OnValueChanged() { /*...*/ } }
The static object.ReferenceEquals method
class Widget { // Let's suppose Widget overrides its Equals method and overloads its == operator such // that w1.Equals (w2) would return true if w1 and w2 were different objects. /*...*/ } static void Main() { Widget w1 = new Widget(); Widget w2 = new Widget(); Console.WriteLine (object.ReferenceEquals (w1, w2)); // False }
The IEquatable
IEquatable<T> implementation gives the same result as calling object’s virtual Equals method.
class Testwhere T : IEquatable { public bool IsEqual (T a, T b) => a.Equals (b); // No boxing with generic T } static void Main() { new Test ().IsEqual (3, 3).Dump(); }
Removing the generic constraint, the class will still compile but will bind to the slower object.Equals.
When Equals and == are not equal
The double type’s == operator ensures that double.NaN cannot equal to anything else. It will always result as false. But Equals method will always apply reflexive equality.
Collections and dictionaries always rely on Equals behaving this way.
// With value types, it's quite rare: double x = double.NaN; Console.WriteLine (x == x); // False Console.WriteLine (x.Equals (x)); // True // With reference types, it's more common: var sb1 = new StringBuilder ("foo"); var sb2 = new StringBuilder ("foo"); Console.WriteLine (sb1 == sb2); // False (referential equality) Console.WriteLine (sb1.Equals (sb2)); // True (value equality)
Equality and Custom Types
Value types use value equality.
Reference types use referential equality.
Struct’s Equals method applies structural value equality by default.
When to override the equality method
When there is a need to change the meaning of equality.
When there is need to speed up equality comparisons for structs.
public struct Area : IEquatable { public readonly int Measure1; public readonly int Measure2; public Area (int m1, int m2) { Measure1 = Math.Min (m1, m2); Measure2 = Math.Max (m1, m2); } public override bool Equals (object other) { if (!(other is Area)) return false; return Equals ((Area) other); // Calls method below } public bool Equals (Area other) // Implements IEquatable => Measure1 == other.Measure1 && Measure2 == other.Measure2; public override int GetHashCode() => Measure2 * 31 + Measure1; // 31 = some prime number public static bool operator == (Area a1, Area a2) => a1.Equals (a2); public static bool operator != (Area a1, Area a2) => !a1.Equals (a2); } static void Main() { Area a1 = new Area (5, 10); Area a2 = new Area (10, 5); Console.WriteLine (a1.Equals (a2)); // True Console.WriteLine (a1 == a2); // True }