C# – Order Comparison

Order Comparison

Protocols are:

1. The IComparable Interfaces

2. The < and > Operators

IComparable Interfaces

Used for general-purpose sorting algorithms.

System.String implements IComparable interfaces.

string[] colors = { "Green", "Red", "Blue" };
Array.Sort (colors);
foreach (string c in colors) Console.Write (c + " ");   // Blue Green Red

// The IComparable interfaces are defined as follows:
//   public interface IComparable       { int CompareTo (object other); }
//   public interface IComparable { int CompareTo (T other);      }

Console.WriteLine ("Beck".CompareTo ("Anne"));       // 1
Console.WriteLine ("Beck".CompareTo ("Beck"));       // 0
Console.WriteLine ("Beck".CompareTo ("Chris"));      // -1

If a comes after b, a.CompareTo(b) returns a positive number.

If a is the same as b, a.CompareTo(b) returns 0.

If a comes before b, a.CompareTo(b) returns a negative number.

< and > Operators

The < and > operators are more specialized and mostly used for numeric types. They are efficient for bytecode translation and used for intensive algorithms.

// Some types define < and > operators:
bool after2010 = DateTime.Now > new DateTime (2010, 1, 1);

// The string type doesn't overload these operators (for good reason):
bool error = "Beck" > "Anne";       // Compile-time error
public struct Note : IComparable, IEquatable, IComparable
{
	int _semitonesFromA;
	public int SemitonesFromA => _semitonesFromA;
	
	public Note (int semitonesFromA)
	{
		_semitonesFromA = semitonesFromA;
	}
	
	public int CompareTo (Note other)            // Generic IComparable
	{
		if (Equals (other)) return 0;    // Fail-safe check
		return _semitonesFromA.CompareTo (other._semitonesFromA);
	}
	
	int IComparable.CompareTo (object other)     // Nongeneric IComparable
	{
		if (!(other is Note))
			throw new InvalidOperationException ("CompareTo: Not a note");
		return CompareTo ((Note) other);
	}
	
	public static bool operator < (Note n1, Note n2)
		=> n1.CompareTo (n2) < 0;
	
	public static bool operator > (Note n1, Note n2)
		=> n1.CompareTo (n2) > 0;
	
	public bool Equals (Note other)    // for IEquatable
		=> _semitonesFromA == other._semitonesFromA;
	
	public override bool Equals (object other)
	{
		if (!(other is Note)) return false;
		return Equals ((Note) other);
	}
	
	public override int GetHashCode()
		=> _semitonesFromA.GetHashCode();
	
	public static bool operator == (Note n1, Note n2)
		=> n1.Equals (n2);
	
	public static bool operator != (Note n1, Note n2)
		=> !(n1 == n2);
}

static void Main()
{
	Note n1 = new Note (1);	
	Note n2 = new Note (2);
	(n2 > n1).Dump();
}

C# – Equality Comparison

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 interface

IEquatable<T> implementation gives the same result as calling object’s virtual Equals method.

class Test where 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
}