Search code examples
c#dictionarykeyprimary-keyicomparable

Is IComparable the best way to use to enforce unique keys in Dictionary?


I have a class MyClass, and I want to put it as the key to a Dictionary like so:

Dictionary<MyClass, string> dict = new Dictionary<MyClass, string>();

I want to ensure that MyClass is a unique key, and uniqueness is specified by looking at MyClass.UniqueProperty. My first thought was to overload the == operator, but I found that C# does not allow this. Then I found the IComparable interface. Will this do the trick? Or should I overload Object.Equals(obj)?


Solution

  • Dictionary is well prepared for customized equality rules. That's why it has a constructor that takes IEqualityComparer (https://msdn.microsoft.com/en-us/library/ms132072(v=vs.110).aspx).

    Since you only care about equality in the context of the dictionary, IEqualityComparer<MyClass> is the most straight-forward solution.

    Primitive example:

    void Main()
    {
      var dict = new Dictionary<MyClass, string>(new MyClassUniqueIdEqualityComparer());
    
      dict.Add(new UserQuery.MyClass { UniqueId = 1 }, "Hi!");
    
      dict.ContainsKey(new UserQuery.MyClass { UniqueId = 2 }).Dump(); // False
      dict.ContainsKey(new UserQuery.MyClass { UniqueId = 1 }).Dump(); // True
    }
    
    public class MyClass
    {
      public int UniqueId { get; set; }
    }
    
    public class MyClassUniqueIdEqualityComparer : IEqualityComparer<MyClass>
    {
      public bool Equals(MyClass a, MyClass b) 
      {
        return a.UniqueId == b.UniqueId;
      }
    
      public int GetHashCode(MyClass a)
      {
        return a.UniqueId.GetHashCode();
      }
    }
    

    The main benefit is that the equality rules only apply as defined by the equality comparer. You don't have to ensure proper equality between e.g. a derived class and a base class - it's all just within the contract of the dictionary and the comparer. Since the Equals and GetHashCode methods aren't virtual with respect to the dictionary, they allow equality even between different types, as long as they implement the same interface - something you really don't want to do with object.Equals, IEquatable<T> and IComparable<T>.