330 likes | 510 Views
Programming in C# Equality. CSE 494R (proposed course for 459 Programming in C#) 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 494R (proposed course for 459 Programming in C#) 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 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 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#Equality CSE 494R (proposed course for 459 Programming in C#) Prof. Roger Crawfis