Programming in C# Equality and Comparison

advertisement

Programming in C#

Equality

CSE / ECE 668

Prof. Roger Crawfis

Equality

 What does it mean for to variables to be equal:

 Numeric types – easy

 Strings – some caveats

 Employee records - ?

Equality

 Two basic types of equality:

 Value equality

 Two variables are equal if they have the same value

(mean the same thing).

 Referential equality

 Two variables are equal if they refer to the same instance (pointer or storage equality).

 Difference:

Are two 2008 Lamborghini Gallardo’s equal?

 Theoretically, it depends on the context .

Default Equality

 As expected, all value types have valuebased equality.

double a = 1.2; double b = 1.2; bool areEqual = a == b; // true.

 Strings also have value equality.

string name = “Crawfis” ; string instructor = “Crawfis” ; bool areEqual = a == b; // true.

Default Equality

 What is the default equality for classes?

 In C++ there is none.

 In C# there is. It is referential equality.

class foo {…}; foo A = new foo(); foo B = new foo(); bool areEqual = A == B; // false .

object C = A; // C refers to the same instance as A.

bool areEqual = C == A; // true .

Default Equality

 Subtleties: int [] a = {0,1,2,3}; int [] b = {0,1,2,3}; int [] a = {0,1,2,3}; int [] b = a; int [] a = null ; int [] b = null ; a == b => false a == b => true a == b => true!

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 runtime 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; t1.x = 3; test[t1] = 4;

Adds a new entry to the 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

{ namespace OSU.Gambling.Craps

struct DiceRoll : IEquatable < DiceRoll >

{

{ internal DiceRoll( int die1, int die2) this .die1 = die1; this .die2 = die2;

} private int die1, die2;

Example - Craps

Alas structs

I’ve never played this game.

{ namespace

I really want to

OSU.Gambling.Craps

constructor this constructor struct DiceRoll : IEquatable < DiceRoll >

{

{ internal DiceRoll( int die1, int die2) this .die1 = die1; this .die2 = die2;

} private int die1, die2;

Requires two dice.

Example - Craps public int Die1 { get { return die1+1; } } public int Die2 { get { return die2+1; } }

Add one to allow for a default value of zero.

Error checking should be done to ensure that die1 and die2 lie between 0 and 5.

Example - Craps

}

{ public bool Equals( DiceRoll other) return die1 == other.die1 && die2 == other.die2

|| die1 == other.die2 && die2 == other.die1;

{

} public override bool Equals( object other) if (!(other is DiceRoll )) return false ; return Equals(( DiceRoll )other); // unbox the struct and compare.

Example - Craps

}

{ public static bool operator ==( DiceRoll die1, DiceRoll die2) return die1.Equals(die2);

{

} public static bool operator !=( DiceRoll die1, DiceRoll die2) return !die1.Equals(die2);

Example - Craps

}

{ public override int GetHashCode() return die1*11 + die2;

}

{ public override int GetHashCode() if (die1 > die2) return 11 * die1 + die2; else return 11 * die2 + die1;

Example - Craps

}

{ public override int GetHashCode() return die1*11 + die2;

}

{ public override int GetHashCode() if (die1 > die2) return 11 * die1 + die2; else return 11 * die2 + die1;

Example - Craps

}

}

{ public static DiceRoll RollDice()

DiceRoll roll = new DiceRoll (); roll.die1 = random.Next(0,5); roll.die2 = random.Next(0,5); return roll;

}

{ private static readonly Random random; static DiceRoll() random = new Random (System.

DateTime .Now.Millisecond);

Design Principle

 The previous code illustrated a good design principle you should follow:

Ensure that zero is a valid state for all value types.

 Dice only have values from 1 to 6, so to make 0 a valid state we add one on the output.

Avoiding all of this

 The previous example can be made trivial and avoid all of this by simply requiring that die1 always be greater than die2 in the implementation.

 Control the creation.

 Works theoretically, but you may want the die to look more random for presentation purposes.

Example2 – Craps class

{ public class DiceRoll : IEquatable < DiceRoll >

}

{ public bool Equals( DiceRoll other) if (other == null ) return false ; return (die1 == other.die1 && die2 == other.die2)

|| (die1 == other.die2 && die2 == other.die1);

Example2 – Craps class

{ public override bool Equals( object other) if (other == null ) return false ; if ( object .ReferenceEquals( this , other)) return true ; if ( this .GetType() != other.GetType()) return false ; return Equals(other as DiceRoll );

}

Example2 – Craps class

}

{

… public static DiceRoll RollDice() return new DiceRoll (random.Next(0,5), random.Next(0,5));

 The only way to create a DiceRoll now.

Programming in C#

Comparison (Sorting)

CSE / ECE 668

Prof. Roger Crawfis

Comparison

 Comparing two instances of a type has many of the same properties and pitfalls as testing for equality.

 Equality is generally more fussy.

 Only two protocols for a type providing its own comparison:

 The IComparable interface

 The > and < operators

IComparable

 The IComparable<T> and IComparable interfaces allow for sorting or ordering of a collection.

 They are used in the Array.Sort method.

} public interface IComparable <T>{ int CompareTo (T other); // -1 if this < other, 0 if this == other, 1 if this > other

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.

Programming in C#

Changing Comparison

CSE 494R

(proposed course for 459 Programming in C#)

Prof. Roger Crawfis

IComparer

 IComparer is used to provide pluggable (or interchangable) comparisons.

 Used with a type, not part of the type.

} public interface IComparer { int Compare ( object x, object y); // -1 if x < y, 0 if x == y, 1 if x > y

 IComparer implementations:

 Comparer, CaseInsensitiveComparer: for string comparisons

Custom IComparer

 Creation of table of strings: string[][] Table = { new string[] {"John", "Dow", "programmer"}, new string[] {"Bob", "Smith", "agent"}, new string[] {"Jane", "Dow", "assistant"}, new string[] {"Jack", "Sparrow", "manager"}

};

 Printing the table: foreach (string[] Row in Table) {

Console.WriteLine( String.Join

(", ", Row));

}

Custom IComparer

Comparer for single table (array) column: class ArrayComparer<T> : IComparer<T[]> where T : IComparable<T> { private int m_Index; public ArrayComparer( int index) { this>index = index;

} public int Compare(T[] x, T[] y) { return x[index ].CompareTo( y[index ] );

}

}

Printing the table:

Array.Sort(Employees, new ArrayComparer< string >(2));

} foreach (string[] Row in Employees) {

Console.WriteLine(String.Join(", ", Row));

Bob, Smith, agent

Jane, Dow, assistant

Jack, Sparrow, manager

John, Dow, programmer

Custom IComparer

 Implement IComparable and IComparable<T>

} public interface IComparable { int CompareTo(object obj); // -1: this < obj, 0: this == obj, 1: this > obj

} public interface IComparable<T> { int CompareTo(T obj); // -1: this < obj, 0: this == obj, 1: this > obj class Fraction : IComparable, IComparable<Fraction> { private int n, d;

} public int CompareTo(object o) { return CompareTo((Fraction) o);

}

} public int CompareTo(Fraction f) { return n*f.d

– f.n*d

Download