0

My question is basically the opposite of Dictionary.ContainsKey return False, but a want True and of "the given key was not present in the dictionary" error when using a self-defined class as key:
I want to use a medium-sized class as the dictionary's key, and the dictionary must compare the keys by reference, not by value equality. The problem is, that the class already implements Equals() (which is performing value equality - which is what not what I want here).

Here's a small test class for reproduction:

class CTest
{
  public int m_iValue;

  public CTest (int i_iValue)
  {
    m_iValue = i_iValue;
  }

  public override bool Equals (object i_value)
  {
    if (ReferenceEquals (null, i_value))
      return false;
    if (ReferenceEquals (this, i_value))
      return true;

    if (i_value.GetType () != GetType ())
      return false;

    return m_iValue == ((CTest)i_value).m_iValue;
  }

}

I have NOT yet implemented GetHashCode() (actually I have, but it only returns base.GetHashCode() so far).

Now I created a test program with a dictionary that uses instances of this class as keys. I can add multiple identical instances to the dictionary without problems, but this only works because GetHashCode() returns different values:

private static void Main ()
{
  var oTest1 = new CTest (1);
  var oTest2 = new CTest (1);
  bool bEquals = Equals (oTest1, oTest2);   // true
  var dict = new Dictionary<CTest, int> ();
  dict.Add (oTest1, 1);
  dict.Add (oTest2, 2);         // works
  var iValue1 = dict[oTest1];   // correctly returns 1
  var iValue2 = dict[oTest2];   // correctly returns 2
  int iH1 = oTest1.GetHashCode ();   // values different on each execution
  int iH2 = oTest2.GetHashCode ();   // values different on each execution, but never equals iH1
}

And the hash values are different every time, maybe because the calculatation in object.GetHashCode() uses some randomization or some numbers that come from the reference handle (which is different for each object).
However, this answer on Why is it important to override GetHashCode when Equals method is overridden? says that GetHashCode() must return the same values for equal objects, so I added

  public override int GetHashCode ()
  {
    return m_iValue;
  }

After that, I could not add multiple equal objects to the dictionary any more.

Now, there are two conclusions:

  1. If I removed my own GetHashCode() again, the hash values will be different again and the dictionary can be used. But there may be situations that accidentally give the same hash code for two equal objects, which will cause an exception at runtime, whose cause will for sure never be found. Because of that (little, but not zero) risk, I cannot use a dictionary.
  2. If I correctly implement GetHashCode() like I am supposed to do, I cannot use a dictionary anyway.

What possibilities exist to still use a dictionary?

mjwills
  • 23,389
  • 6
  • 40
  • 63
Tobias Knauss
  • 3,361
  • 1
  • 21
  • 45
  • You can get duplicate hashes by using a separator between objects. For example if x = hash(a), y = hash(b), and xy = hash(c). So you have xy = hash(a) + hash(b) = hash(c). So to prevent duplicates use a separator like '^' So the hash x^y = hash(a) + hash(b) while xy = hash(c). – jdweng Jul 14 '19 at 12:51

1 Answers1

0

Like many times before, I had the idea for a solution when writing this question.

You can specify an IEqualityComparer<TKey> in the constructor of the dictionary. There is one in the .net framework, but it's internal sealed, so you need to implement your own:
Is there any kind of "ReferenceComparer" in .NET?

internal class ReferenceComparer<T> : IEqualityComparer<T> where T : class
{
  static ReferenceComparer ()
  {
    Instance = new ReferenceComparer<T> ();
  }

  public static ReferenceComparer<T> Instance { get; }

  public bool Equals (T x, T y)
  {
    return ReferenceEquals (x, y);
  }

  public int GetHashCode (T obj)
  {
    return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode (obj);
  }
}
Tobias Knauss
  • 3,361
  • 1
  • 21
  • 45