I created the below comparer to allow me to use a generic dictionary as a key to another generic dictionary.
My GetHashCode
implementation creates a hash based on all keys and their values; but I suspect its distribution could be improved?
Also my Equals
method returns true only if the two arguments have exactly the same keys, with each key having exactly the same values. However, potentially there's a more efficient approach to this comparison which I've neglected?
using System.Collections.Generic;
using System.Linq;
namespace MyCompany.Collections.Generic
{
public class DictionaryEqualityComparer<TKey, TValue> : IEqualityComparer<IDictionary<TKey, TValue>>
{
readonly IEqualityComparer<TKey> keyComparer;
readonly IEqualityComparer<TValue> valueComparer;
public DictionaryEqualityComparer(): this(null, null){}
public DictionaryEqualityComparer(IEqualityComparer<TKey> keyComparer, IEqualityComparer<TValue> valueComparer)
{
this.keyComparer = keyComparer ?? EqualityComparer<TKey>.Default;
this.valueComparer = valueComparer ?? EqualityComparer<TValue>.Default;
}
public bool Equals(IDictionary<TKey, TValue> a, IDictionary<TKey, TValue> b)
{
if (a == null || b == null) return (a == null && b == null); //if either value is null return false, or true if both are null
return a.Count == b.Count //unless they have the same number of items, the dictionaries do not match
&& a.Keys.Intersect(b.Keys, keyComparer).Count() == a.Count //unless they have the same keys, the dictionaries do not match
&& a.Keys.Where(key => ValueEquals(a[key], b[key])).Count() == a.Count; //unless each keys' value is the same in both, the dictionaries do not match
}
public int GetHashCode(IDictionary<TKey, TValue> obj)
{
//I suspect there's a more efficient formula for even distribution, but this does the job for now
long hashCode = obj.Count;
foreach (var key in obj.Keys)
{
hashCode += (key?.GetHashCode() ?? 1000) + (obj[key]?.GetHashCode() ?? 0);
hashCode %= int.MaxValue; //ensure we don't go outside the bounds of MinValue-MaxValue
}
return (int)hashCode; //safe conversion thanks to the above %
}
private bool ValueEquals(TValue x, TValue y)
{
return valueComparer.Equals(x, y);
}
}
}
GetHashCode
method doesn't handle nulls; so have since amended the first line toif (obj == null) return 0; long hashCode = obj.Count + 1;
; so that null is handled, and an empty dictionary has a different hashcode to a null one. \$\endgroup\$