0% found this document useful (0 votes)
37 views36 pages

Programming in C#: Equality and Comparison

This document discusses equality and comparison in C#, including value equality vs reference equality, default equality for different types, and overriding equality operators and methods. It covers the == and != operators, Object.Equals, IEquatable<T> interface, and GetHashCode method. It provides examples of implementing equality for a DiceRoll struct.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
37 views36 pages

Programming in C#: Equality and Comparison

This document discusses equality and comparison in C#, including value equality vs reference equality, default equality for different types, and overriding equality operators and methods. It covers the == and != operators, Object.Equals, IEquatable<T> interface, and GetHashCode method. It provides examples of implementing equality for a DiceRoll struct.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 36

Programming in C#

Equality and comparison


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;
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}; a == b => false

int[] b = {0,1,2,3};

int[] a = {0,1,2,3}; a == b => true

int[] b = a;

int[] a = null; a == b => true!

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; } }

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 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.


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.

You might also like