web-dev-qa-db-fra.com

Dictionnaire multi-clé en c #?

Je sais qu'il n'y en a pas dans la BCL, mais quelqu'un peut-il m'indiquer un bon fournisseur open source?

Par Multi, je veux dire 2 clés. ;-)

102
SuperSuperDev1234

J'ai également utilisé des tuples comme jason dans sa réponse fait. Cependant, je vous suggère simplement de définir un Tuple en tant que struct:

public struct Tuple<T1, T2> {
    public readonly T1 Item1;
    public readonly T2 Item2;
    public Tuple(T1 item1, T2 item2) { Item1 = item1; Item2 = item2;} 
}

public static class Tuple { // for type-inference goodness.
    public static Tuple<T1,T2> Create<T1,T2>(T1 item1, T2 item2) { 
        return new Tuple<T1,T2>(item1, item2); 
    }
}

Vous bénéficiez de l'immuabilité, du .GetHashcode et du .Equals gratuitement, ce qui (en attendant C # 4.0) est simple ...

Un avertissement cependant: la valeur par défaut GetHashcode implementation (parfois) considère uniquement le premier champ donc assurez-vous que le premier champ est le plus discriminant ou implémentez vous-même GetHashcode (par exemple, en utilisant FieldwiseHasher.Hash(this) à partir de ValueUtils ), sinon vous rencontrerez probablement des problèmes d’évolutivité.

En outre, vous évitez les valeurs NULL qui ont tendance à compliquer les choses (et si vous voulez vraiment des NULL, vous devez simplement rendre votre Tuple<> nullable). Légèrement décalé, suis-je le seul à être contrarié par le manque de prise en charge au niveau de la structure des références non nulles? Je travaille sur un projet volumineux, et parfois une valeur nulle pénètre quelque part où elle ne devrait pas - et hop, vous obtenez une exception nullreference - mais avec une trace de pile qui vous indique la première utilisation de la référence, pas le code réellement défectueux. .

Bien sûr, .NET 4.0 est assez vieux maintenant; la plupart d'entre nous peuvent simplement utiliser le Tuple de .NET 4.0.

Edit: pour contourner la pauvre implémentation GetHashCode que .NET fournit aux structures que j'ai écrites ValueUtils , ce qui vous permet également d'utiliser des noms réels pour vos clés multi-champs; cela signifie que vous pourriez écrire quelque chose comme:

sealed class MyValueObject : ValueObject<MyValueObject> {
    public DayOfWeek day;
    public string NamedPart;
    //properties work fine too
}

... ce qui, espérons-le, facilite la création de noms lisibles par l'homme pour les données avec une sémantique de valeur, du moins jusqu'à ce que une version future de C # implémente les n-uplets appropriés avec des membres nommés ; j'espère avec des codes de hachage décents ;-).

64
Eamon Nerbonne

J'utilise un Tuple comme clés dans un Dictionary.

public class Tuple<T1, T2> {
    public T1 Item1 { get; private set; }
    public T2 Item2 { get; private set; }

    // implementation details
}

Veillez à remplacer Equals et GetHashCode et à définir operator!= et operator== selon le cas. Vous pouvez développer la variable Tuple pour contenir plus d'éléments si nécessaire. .NET 4.0 inclura une Tuple intégrée.

53
jason

Les tuples seront (sont) en .Net 4.0 Jusque-là, vous pouvez aussi utiliser un 

 Dictionary<key1, Dictionary<key2, TypeObject>> 

ou, créer une classe de collection personnalisée pour représenter cette ...

 public class TwoKeyDictionary<K1, K2, T>: 
        Dictionary<K1, Dictionary<K2, T>> { }

ou, avec trois clés ...

public class ThreeKeyDictionary<K1, K2, K3, T> :
    Dictionary<K1, Dictionary<K2, Dictionary<K3, T>>> { }
30
Charles Bretana

Beaucoup de bonnes solutions ici, Ce qui me manque ici est une implémentation basée sur le type construit dans Tuple, alors j’en ai écrit une moi-même.

Comme il hérite simplement de Dictionary<Tuple<T1,T2>, T>, vous pouvez toujours utiliser les deux méthodes.

var dict = new Dictionary<int, int, Row>();
var row = new Row();
dict.Add(1, 2, row);
dict.Add(Tuple.Create(1, 2, row));
dict.Add(new Tuple<int, int>(1, 2));

voici le code.

public class Dictionary<TKey1,TKey2,TValue> :  Dictionary<Tuple<TKey1, TKey2>, TValue>, IDictionary<Tuple<TKey1, TKey2>, TValue>
{

    public TValue this[TKey1 key1, TKey2 key2]
    {
        get { return base[Tuple.Create(key1, key2)]; }
        set { base[Tuple.Create(key1, key2)] = value; }
    }

    public void Add(TKey1 key1, TKey2 key2, TValue value)
    {
        base.Add(Tuple.Create(key1, key2), value);
    }

    public bool ContainsKey(TKey1 key1, TKey2 key2)
    {
        return base.ContainsKey(Tuple.Create(key1, key2));
    }
}

Sachez que cette implémentation dépend de celle de Tuple.Equals ():

http://msdn.Microsoft.com/en-us/library/dd270346(v=vs.110).aspx

Le paramètre obj est considéré égal à l'instance actuelle dans les conditions suivantes: 

  • C'est un objet Tuple. 
  • Ses deux composants sont du même type que l'instance actuelle. 
  • Ses deux composants sont égaux à ceux de l'instance actuelle. L'égalité est déterminée par le comparateur d'égalité d'objet par défaut pour chaque composant.
16

J'ai écrit et l'ai utilisé avec succès.

public class MultiKeyDictionary<K1, K2, V> : Dictionary<K1, Dictionary<K2, V>>  {

    public V this[K1 key1, K2 key2] {
        get {
            if (!ContainsKey(key1) || !this[key1].ContainsKey(key2))
                throw new ArgumentOutOfRangeException();
            return base[key1][key2];
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new Dictionary<K2, V>();
            this[key1][key2] = value;
        }
    }

    public void Add(K1 key1, K2 key2, V value) {
            if (!ContainsKey(key1))
                this[key1] = new Dictionary<K2, V>();
            this[key1][key2] = value;
    }

    public bool ContainsKey(K1 key1, K2 key2) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2);
    }

    public new IEnumerable<V> Values {
        get {
            return from baseDict in base.Values
                   from baseKey in baseDict.Keys
                   select baseDict[baseKey];
        }
    } 

}


public class MultiKeyDictionary<K1, K2, K3, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, V>> {
    public V this[K1 key1, K2 key2, K3 key3] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, V>();
            this[key1][key2, key3] = value;
        }
    }

    public bool ContainsKey(K1 key1, K2 key2, K3 key3) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, V>();
            this[key1][key2, key3, key4] = value;
        }
    }

    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, V>();
            this[key1][key2, key3, key4, key5] = value;
        }
    }

    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, V>();
            this[key1][key2, key3, key4, key5, key6] = value;
        }
    }
    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6, key7] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, V>();
            this[key1][key2, key3, key4, key5, key6, key7] = value;
        }
    }
    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6, key7);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, K8, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6, key7, key8] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, V>();
            this[key1][key2, key3, key4, key5, key6, key7, key8] = value;
        }
    }
    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6, key7, key8);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, K8, K9, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6, key7, key8, key9] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, V>();
            this[key1][key2, key3, key4, key5, key6, key7, key8, key9] = value;
        }
    }
    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6, key7, key8, key9);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, K10, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9, K10 key10] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6, key7, key8, key9, key10] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, K10, V>();
            this[key1][key2, key3, key4, key5, key6, key7, key8, key9, key10] = value;
        }
    }
    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9, K10 key10) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6, key7, key8, key9, key10);
    }
}

public class MultiKeyDictionary<K1, K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, V>> {
    public V this[K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9, K10 key10, K11 key11] {
        get {
            return ContainsKey(key1) ? this[key1][key2, key3, key4, key5, key6, key7, key8, key9, key10, key11] : default(V);
        }
        set {
            if (!ContainsKey(key1))
                this[key1] = new MultiKeyDictionary<K2, K3, K4, K5, K6, K7, K8, K9, K10, K11, V>();
            this[key1][key2, key3, key4, key5, key6, key7, key8, key9, key10, key11] = value;
        }
    }
    public bool ContainsKey(K1 key1, K2 key2, K3 key3, K4 key4, K5 key5, K6 key6, K7 key7, K8 key8, K9 key9, K10 key10, K11 key11) {
        return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3, key4, key5, key6, key7, key8, key9, key10, key11);
    }
}
11

Je suis actuellement en train de concaténer les clés dans une seule chaîne pour résoudre ce problème Bien sûr, cela ne fonctionnera pas sur les clés non-chaîne. J'aimerais connaître la réponse aussi.

6
Adrian Godong

Jetez un coup d'oeil à PowerCollections ( CodePlex download ) de Wintellect. Je pense que leur MultiDictionary fait quelque chose comme ça. 

C'est un dictionnaire de dictionnaires, vous avez donc 2 clés pour accéder à chaque objet, la clé du dictionnaire principal pour obtenir le sous-dictionnaire requis, puis la deuxième clé du sous-dictionnaire pour obtenir l'élément souhaité. Est-ce ce que vous voulez dire?

6
Simon P Stevens

Je l'utilise souvent parce que c'est court et fournit le sucre syntaxique dont j'ai besoin ...

public class MultiKeyDictionary<T1, T2, T3> : Dictionary<T1, Dictionary<T2, T3>>
{
    new public Dictionary<T2, T3> this[T1 key]
    {
        get
        {
            if (!ContainsKey(key))
                Add(key, new Dictionary<T2, T3>());

            Dictionary<T2, T3> returnObj;
            TryGetValue(key, out returnObj);

            return returnObj;
        }
    }
}

Pour l'utiliser:

dict[cat][fish] = 9000;

où la clé "Cat" ne doit pas exister non plus.

6
max

Y a-t-il un problème avec 

nouveau dictionnaire <KeyValuePair <objet, objet>, objet>
?

6
JSBձոգչ

J'ai googlé pour celui-ci: http://www.codeproject.com/KB/recipes/multikey-dictionary.aspx . Je suppose que sa principale caractéristique par rapport à l'utilisation de struct pour contenir 2 clés dans le dictionnaire normal est que vous pouvez faire référence ultérieurement à l'une des clés au lieu de devoir fournir 2 clés.

4
Marcin Deptuła

Si quelqu'un cherche un ToMultiKeyDictionary(), voici une implémentation qui devrait fonctionner avec la plupart des réponses suivantes (basée sur Herman's ):

public static class Extensions_MultiKeyDictionary {

    public static MultiKeyDictionary<K1, K2, V> ToMultiKeyDictionary<S, K1, K2, V>(this IEnumerable<S> items, Func<S, K1> key1, Func<S, K2> key2, Func<S, V> value) {
        var dict = new MultiKeyDictionary<K1, K2, V>(); 
        foreach (S i in items) { 
            dict.Add(key1(i), key2(i), value(i)); 
        } 
        return dict; 
    }

    public static MultiKeyDictionary<K1, K2, K3, V> ToMultiKeyDictionary<S, K1, K2, K3, V>(this IEnumerable<S> items, Func<S, K1> key1, Func<S, K2> key2, Func<S, K3> key3, Func<S, V> value) {
        var dict = new MultiKeyDictionary<K1, K2, K3, V>(); 
        foreach (S i in items) { 
            dict.Add(key1(i), key2(i), key3(i), value(i)); 
        } 
        return dict; 
    }
}
3
katbyte

Je pense que vous auriez besoin d'un Tuple2 comme classe. Assurez-vous que c'est GetHashCode () et Equals () est basé sur les deux éléments contenus.

Voir Tuples en C #

2
Paul Ruane

Pourriez-vous utiliser un Dictionary<TKey1,Dictionary<TKey2,TValue>>?

Vous pouvez même sous-classer ceci:

public class DualKeyDictionary<TKey1,TKey2,TValue> : Dictionary<TKey1,Dictionary<TKey2,TValue>>

EDIT: Ceci est maintenant une réponse en double. Il est également limité dans sa pratique. Bien que cela "fonctionne" et offre la possibilité de coder dict[key1][key2], il existe de nombreuses "solutions de contournement" pour que cela fonctionne "tout simplement".

CEPENDANT: Juste pour le plaisir, on pourrait néanmoins implémenter Dictionary, mais à ce stade, il devient un peu prolixe:

public class DualKeyDictionary<TKey1, TKey2, TValue> : Dictionary<TKey1, Dictionary<TKey2, TValue>> , IDictionary< object[], TValue >
{
    #region IDictionary<object[],TValue> Members

    void IDictionary<object[], TValue>.Add( object[] key, TValue value )
    {
        if ( key == null || key.Length != 2 )
            throw new ArgumentException( "Invalid Key" );

        TKey1 key1 = key[0] as TKey1;
        TKey2 key2 = key[1] as TKey2;

        if ( !ContainsKey( key1 ) )
            Add( key1, new Dictionary<TKey2, TValue>() );

        this[key1][key2] = value;
    }

    bool IDictionary<object[], TValue>.ContainsKey( object[] key )
    {
        if ( key == null || key.Length != 2 )
            throw new ArgumentException( "Invalid Key" );

        TKey1 key1 = key[0] as TKey1;
        TKey2 key2 = key[1] as TKey2;

        if ( !ContainsKey( key1 ) )
            return false;

        if ( !this[key1].ContainsKey( key2 ) )
            return false;

        return true;
    }
2
maxwellb

Voici un exemple complet d'une classe de paires pouvant être utilisée comme clé d'un dictionnaire.

  public class Pair<T1, T2> {
    public T1 Left { get; private set; }
    public T2 Right { get; private set; }

    public Pair(T1 t1, T2 t2) {
      Left=t1;
      Right=t2;
    }

    public override bool Equals(object obj) {
      if(ReferenceEquals(null, obj)) return false;
      if(ReferenceEquals(this, obj)) return true;
      if(obj.GetType()!=typeof(Pair<T1, T2>)) return false;
      return Equals((Pair<T1, T2>)obj);
    }

    public bool Equals(Pair<T1, T2> obj) {
      if(ReferenceEquals(null, obj)) return false;
      if(ReferenceEquals(this, obj)) return true;
      return Equals(obj.Left, Left) && Equals(obj.Right, Right);
    }

    public override int GetHashCode() {
      unchecked {
        return (Left.GetHashCode()*397)^Right.GetHashCode();
      }
    }
  }
2
Michael Donohue

Voici ma mise en œuvre. Je voulais quelque chose pour cacher la mise en œuvre du concept Tuple.

  public class TwoKeyDictionary<TKey1, TKey2, TValue> : Dictionary<TwoKey<TKey1, TKey2>, TValue>
  {
    public static TwoKey<TKey1, TKey2> Key(TKey1 key1, TKey2 key2)
    {
      return new TwoKey<TKey1, TKey2>(key1, key2);
    }

    public TValue this[TKey1 key1, TKey2 key2]
    {
      get { return this[Key(key1, key2)]; }
      set { this[Key(key1, key2)] = value; }
    }

    public void Add(TKey1 key1, TKey2 key2, TValue value)
    {
      Add(Key(key1, key2), value);
    }

    public bool ContainsKey(TKey1 key1, TKey2 key2)
    {
      return ContainsKey(Key(key1, key2));
    }
  }

  public class TwoKey<TKey1, TKey2> : Tuple<TKey1, TKey2>
  {
    public TwoKey(TKey1 item1, TKey2 item2) : base(item1, item2) { }

    public override string ToString()
    {
      return string.Format("({0},{1})", Item1, Item2);
    }
  }

Il aide à garder l'utilisation ressemblant à un dictionnaire

item.Add(1, "D", 5.6);

value = item[1, "D"];
1
Eric

Voici un autre exemple d'utilisation de la classe Tuple avec le dictionnaire.

        // Setup Dictionary
    Dictionary<Tuple<string, string>, string> testDictionary = new Dictionary<Tuple<string, string>, string>
    {
        {new Tuple<string, string>("key1","key2"), "value1"},
        {new Tuple<string, string>("key1","key3"), "value2"},
        {new Tuple<string, string>("key2","key3"), "value3"}
    };
    //Query Dictionary
    public string FindValue(string stuff1, string stuff2)
    {
        return testDictionary[Tuple.Create(stuff1, stuff2)];
    }
0
Justin