Kamarád měl na mě zajímavý dotaz. Chtěl mít instanci IDictionary<TKey, TValue> a používat ji klasickým způsobem, ale protože Dictionary<TKey, TValue> defaultně používá na porovnání klíčů a počítání hashe instanci System.Collections.Generic.EqualityComparer<T>.Default, nedostával očekávané výsledky. Tento comparer totiž kouká na pole jako na jakýkoliv jiný referenční typ, takže v důsledku dojde pouze k porovnání referencí.
Následující kód pak nefunguje tak, jak bychom si představovali – dostaneme KeyNotFoundException.
var variables = new Dictionary<bool?[], string>();
variables.Add(new bool?[] { true, false, null }, "Hi");
var s = variables[new bool?[] { true, false, null }];
Console.WriteLine(s);
První řešení, které mě napadlo, bylo napsat wrapovací třídu, která by obsahovala původní pole a hlavně by vracela hash a měla by přetížené metody pro porovnání tak, aby vše odpovídalo obsahu (ne jen referenci) pole. Jako mnohem lepší řešení jsem ale shledal použít vlastní třídu implementující rozhraní IEqualityComparer<T>, kteroužto můžeme předat jako parametr konstruktoru třídy Dictionary<TKey, TValue>. Výsledkem mého snažení je tato generická třída implementující rozhraní IEqualityComparer<T> pro dané T.
public class ArrayComparer<T> : IEqualityComparer<T[]>
{
public bool Equals(T[] x, T[] y)
{
// first try to use default comparer
if (EqualityComparer<T[]>.Default.Equals(x, y) || (x == null && y == null))
{
return true;
}
if (x == null || y == null || x.Length != y.Length)
{
return false;
}
var comparer = EqualityComparer<T>.Default;
for (int i = 0; i < x.Length; i++)
{
if (!comparer.Equals(x[i], y[i]))
{
return false;
}
}
return true;
}
public int GetHashCode(T[] obj)
{
if (obj == null)
{
return 0;
}
var comparer = EqualityComparer<T>.Default;
int res = obj.Length;
for (int i = 0; i < obj.Length; i++)
{
res ^= comparer.GetHashCode(obj[i]);
}
return res;
}
}
Za povšimnutí stojí několik věcí. Jednak se moc nevidí, aby generická třída s generickým typem T implementovala rozhraní pro generický typ T[]. To je zde klíčová záležitost. Dále je zajímavé, jakým způsobem se provádí porovnání jednotlivých prvků pole.
Výše uvedený příklad pak ve své funkční verzi vypadá takto:
var variables = new Dictionary<bool?[], string>(new ArrayComparer<bool?>());
variables.Add(new bool?[] { true, false, null }, "Hi");
var s = variables[new bool?[] { true, false, null }];
Console.WriteLine(s);
Tuto třídu lze tedy použít jako porovnávač klíčů Dictionary<TKey, TValue>, kde chceme jako TKey použít nějaké pole. Škoda, že není defaultní chování takové, aby se bral v potaz také obsah pole.