Programming in C#: Equality and Comparison
Programming in C#: Equality and Comparison
int[] b = {0,1,2,3};
int[] b = a;
int[] b = null;
Default Equality
• The default equality for structs is simply the
pairwise comparison between each of its
fields.
• That is, two structs are equal if all of their
fields are equal.
– Value-based field are compared by value.
– Reference fields are compared by reference (by
default).
Testing for Equality
• There are actually five protocols or methods
that you can use to test for equality.
• They may provide different results!!!
– Witness one says they are the same.
– Witness two says they are different.
Testing for Equality
• Operators for equality (== and !=).
• Recall that all operators are defined as static
methods for a type.
– If there are different types on each side of the
conditional, the compiler decides which type to
use.
• All types have these operators defined.
– Unlike C++.
Testing for Equality
• The Object.Equals method exists for all types.
• It is virtual and hence can be overridden.
• Determines at run-time which type’s Equals
method is called (Polymorphic).
int x = 5;
object y = 5; x == y => compile time error
x.Equals(y) => true
y.Equals(x) => true
Note: x is boxed!
Testing for Equality
• Why have both of these?
– If a variable is null, calling Equals on it will result in
a run-time exception.
– There is overhead associated with virtual function
calls (and all function calls).
– The == operator can be statically inlined.
– Sometimes we want different behavior:
• We both own the same car (equals) but mine is not
yours (not equals).
Testing for Equality
• There are also two static methods in the object
class.
static bool object.Equals(object o1, object o2);
– Simply calls o1.Equals if o1 != null.
static bool
object.ReferenceEquals(object o1, object o2);
– Since Equals can be overridden, this provides a forced
reference equality check.
– Note: object.ReferenceEquals(5,5) = false!
The IEquatable<T> interface
• Although the System.Object.Equals method
can be applied to any type it will force a
boxing of value types.
• The IEquatable<T> interface allows value
types which implement the interface to be
called using a.Equals(b) without the boxing.
• Used as a generic constraint on user classes:
class Test<T> where T : IEquatable<T>
Overriding Equality
• In general, do not change the default behavior
(semantics).
• Implementing the default behavior for structs
and the IComparable<T> interface can avoid
boxing and provide good performance
improvements.
• Some immutable types may want different
semantics: string, DateTime
Overriding Equality
• But what if I have Employee records and I
want to check if two instances are equal?
• Do not build this into the Employee class.
• Use plug-in comparison classes to dynamically
specify the behavior you want.
• This is what is used in the collection classes.
Equality Semantics
• If you override the semantics, then you must
also override the hash code algorithm.
• There are several rules that you should make
sure you follow for each of ==, Equals, and
GetHashCode.
• If you override one of these you should
probably override them all.
Overriding GetHashCode
• Rules for GetHashCode:
– Consistency – it must return the same value if called
repeated on the same object.
• Even if you change the object!!!!
• Base it on an immutable value of the object.
– Equality – it must return the same value on two
objects for which Equals returns true.
– Robust – it should not throw exceptions.
– Efficient – GetHashCode should generate a random
distribution among all inputs.
Overriding GetHashCode
• For reference types, each instance has a unique
hash-code based on the storage location.
• Can add it to a Collection (Dictionary), change
the contents, and still get it back out using the
hash code.
• If you override the hash code based on content
rather than storage location, then the instance
may need to be removed from the Dictionary,
changed and added back.
Overriding GetHashCode
• Structs and value types are different, since they are never
changed in-place, they are copied out, modified and copied
back in.
• Hence this problem exists regardless of whether you override
GetHashCode.
struct X { public int x; }
Dictionary<X, int> test = new Dictionary<X, int>();
X t1 = new X();
test[t1] = 5; Adds a new
t1.x = 3; entry to the
test[t1] = 4; dictionary
Overriding Equals
• Rules for overriding equals:
– An object should equal itself (reflexive).
– An object can not equal null
• Since it is an instance method call.
• Unless it is a Nullable type.
– Equality is commutative and transitive.
• a == b => b == a; b == c => a ==c;
– Equality operations are repeatable
– Equality is robust – no exceptions.
Overriding == and !=
• You should always override == for value types
for efficiency.
• You should never (or rarely) override it for
reference types.
• Rules for overriding ==
– a != b should be equal to !(a == b).
– It should have the same semantics as Equals.
• Hence the previous rules apply.
Example - Craps I’ve never
played this
Alas structs game.
always have a want to
I really
namespace OSU.Gambling.Craps
public default
control the
constructor
creation, so make
{
this constructor
struct DiceRoll : IEquatable<DiceRoll>
internal or private.
{
internal DiceRoll(int die1, int die2)
{
this.die1 = die1;
this.die2 = die2; Requires two
dice.
}
private int die1, die2;
Example - Craps
public int Die1 { get { return die1+1; } }
public int Die2 { get { return die2+1; } }
5.CompareTo(7) => -1
“World”.CompareTo(“Hello”) => 1
32.CompareTo(8*4) => 0
IComparable
• Classes implementing IComparable are
– Values types like Int32, Double, DateTime, …
– The class Enum as base class of all enumeration
types
– The class String
• Defines a type to be is-a IComparable.
The > and < operators
• Value types that have a clear context
independent concept of less than and greater
than should implement the < and > operators.
• These are compiled statically into the code,
making value types more efficient.