IDictionary – TKey jako pole

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.

Zanechat odpověď

Vyplňte detaily níže nebo klikněte na ikonu pro přihlášení:

Logo WordPress.com

Komentujete pomocí vašeho WordPress.com účtu. Odhlásit /  Změnit )

Facebook photo

Komentujete pomocí vašeho Facebook účtu. Odhlásit /  Změnit )

Připojování k %s

Tento web používá Akismet na redukci spamu. Zjistěte více o tom, jak jsou data z komentářů zpracovávána.