Friday 15 June 2007

Understanding IEquatable

Since Generics came along in C# 2.0 we have had a new interface to allow for type safe comparisons. That Interface is IEquatable which is a vast improvemnet over the previous Equals that was hung off object in the BCL.

MSDN Says:

The IEquatable interface is used by generic collection objects such as Dictionary, List, and LinkedList when testing for equality in such methods as Contains, IndexOf, LastIndexOf, and Remove. It should be implemented for any object that might be stored in a generic collection.

If you implement IEquatable, you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behavior is consistent with that of the IEquatable.Equals method. If you do override Object.Equals(Object), your overridden implementation is also called in calls to the static Equals(System.Object, System.Object) method on your class. This ensures that all invocations of the Equals method return consistent results.

So how would I use IEquatable? I always liked using code to demonstrate.

public class IEquatableOverride
{
    private static Dictionary<Person,Person> ObjectDic = new Dictionary<Person,Person>();
    private static Dictionary<int, Person> IntDic = new Dictionary<int, Person>();


        public static void  LoadDictionary()
        {
            Person p = new Person();
            p.PersonId = 101;
            p.First = "John";
            p.Last = "Doe";
            p.Age = 30;
            p.Address = "My Address";

            ObjectDic.Add(p, p);
            IntDic.Add(p.PersonId, p);

            Console.WriteLine(" Can Find Object "+ObjectDic.ContainsKey(p));
            Console.WriteLine(" Can Find by Key " + IntDic.ContainsKey(p.PersonId));


            Person p2 = new Person();
            p2.PersonId = 101;
            p2.First = "John";
            p2.Last = "Doe";
            p2.Age = 30;
            p2.Address = "test";

            ///R1
            Console.WriteLine(" Can Find Object " + ObjectDic.ContainsKey(p2));
            Console.WriteLine(" Can Find by Key " + IntDic.ContainsKey(p2.PersonId));

        }
    }

    public class Person : IEquatable<Person>
    {
        public int PersonId { get; set; }
        public string First { get; set; }
        public string Last { get; set; }
        public int Age { get; set; }
        public string Address { get; set; }

        //Commenting this out result in #R1 being false
        public override int GetHashCode()
        {
            ///In propper use this should be immutable
            return First.GetHashCode() ^ Last.GetHashCode() ^ Age.GetHashCode();
        }

        public bool Equals(Person person)
        {
            if(person == this)
                return true;
            if(person.First == this.First && 
               person.Last == this.Last && 
               person.Age == this.Age)
                    return true;

            return false;
        } 
  }


So as you can see above if we don't override GetHash then when compareing values in a Dictionary we would get erroneous results. This seems to work but why would I override GetHash if I wasn't using the object as the key and why do I need to override Object.Equals I thought that was the whole point of having Generics?

Yes that is the point of generics but there was life before generics one example would be an ArrayList. Anybody remember those bad boys? Well if we create a new ArrayList and add p to it and then compare it to p2 like so:
arrayList.Add(p);
Console.WriteLine("Can Find in ArrayList " + arrayList.Contains(p2));

Can anybody guess what the output of the above line will be? Yep you guessed it False. It is still using the old Object.Equals if we add this to our person class everything turns peachy.


public override bool Equals(object obj)
        {
            Person person = obj as Person;
            if (person == this)
                return true;
            if (person == null)
                return false;

            if (person.First == this.First &&
               person.Last == this.Last &&
               person.Age == this.Age)
                return true;

            return false;
        }

OK but what if we just use generic lists that implement IEquatable so I don't to worry about Object.Equals and I am using a primary key for my Dictionary key. So I am not going to bother with Overridding GetHashCode either.

Nope wrong again, LinQ is also a friend to GetHashCode so even though I am currently not using LinQ functionality in my results if I ever chose to do so and used a method like Distinct or Group By I would not get the results I would expect. So in short even though we have our brand new fancy interface we need to be careful how we use it.


No comments:

Post a Comment