440 likes | 590 Views
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
E N D
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 value-based equality. double a = 1.2; double b = 1.2; boolareEqual = a == b; // true. • Strings also have value equality. string name = “Crawfis”; string instructor = “Crawfis”; boolareEqual = 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. classfoo {…}; foo A = newfoo(); foo B = newfoo(); boolareEqual = A == B; // false. object C = A; // C refers to the same instance as A. boolareEqual = 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 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. structX { publicint x; } Dictionary<X, int> test = newDictionary<X, int>(); X t1 = newX(); 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 namespaceOSU.Gambling.Craps { structDiceRoll : IEquatable<DiceRoll> { internalDiceRoll(int die1, int die2) { this.die1 = die1; this.die2 = die2; } privateint die1, die2;
Example - Craps I’ve never played this game. Alas structs always have a public default constructor I really want to control the creation, so make this constructor internal or private. namespaceOSU.Gambling.Craps { structDiceRoll : IEquatable<DiceRoll> { internalDiceRoll(int die1, int die2) { this.die1 = die1; this.die2 = die2; } privateint die1, die2; Requires two dice.
Example - Craps publicint Die1 { get { return die1+1; } } publicint 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 publicbool Equals(DiceRoll other) { return die1 == other.die1 && die2 == other.die2 || die1 == other.die2 && die2 == other.die1; } publicoverridebool Equals(object other) { if (!(other isDiceRoll)) returnfalse; return Equals((DiceRoll)other); // unbox the struct and compare. }
Example - Craps publicstaticbooloperator ==(DiceRoll die1, DiceRoll die2) { return die1.Equals(die2); } publicstaticbooloperator !=(DiceRoll die1, DiceRoll die2) { return !die1.Equals(die2); }
Example - Craps publicoverrideintGetHashCode() { return die1*11 + die2; } publicoverrideintGetHashCode() { if (die1 > die2) return 11 * die1 + die2; else return 11 * die2 + die1; }
Example - Craps public override int GetHashCode() { return die1*11 + die2; } publicoverrideintGetHashCode() { if (die1 > die2) return 11 * die1 + die2; else return 11 * die2 + die1; }
Example - Craps publicstaticDiceRollRollDice() { DiceRoll roll = newDiceRoll(); roll.die1 = random.Next(0,5); roll.die2 = random.Next(0,5); return roll; } privatestaticreadonlyRandomrandom; staticDiceRoll() { random = newRandom(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 publicclassDiceRoll : IEquatable<DiceRoll> { … publicbool Equals(DiceRoll other) { if (other == null) returnfalse; return(die1 == other.die1 && die2 == other.die2) || (die1 == other.die2 && die2 == other.die1); }
Example2 – Craps class publicoverridebool Equals(object other) { if (other == null) returnfalse; if (object.ReferenceEquals(this, other)) returntrue; if (this.GetType() != other.GetType()) returnfalse; return Equals(other asDiceRoll); } …
Example2 – Craps class … publicstaticDiceRollRollDice() { returnnewDiceRoll(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 interfaceIComparable <T>{ intCompareTo(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-aIComparable.
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 interfaceIComparer { intCompare(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: classArrayComparer<T> : IComparer<T[]> where T : IComparable<T> { private int m_Index; publicArrayComparer(intindex) { this->index = index; } publicint Compare(T[] x, T[] y) { returnx[index ].CompareTo( y[index ] ); } } • Printing the table: Array.Sort(Employees, newArrayComparer<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 IComparableand IComparable<T> public interface IComparable { intCompareTo(object obj); // -1: this < obj, 0: this == obj, 1: this > obj } public interface IComparable<T> { intCompareTo(T obj); // -1: this < obj, 0: this == obj, 1: this > obj } class Fraction : IComparable, IComparable<Fraction> { private int n, d; public intCompareTo(object o) { return CompareTo((Fraction) o); } public intCompareTo(Fraction f) { return n*f.d – f.n*d } }