webentwicklung-frage-antwort-db.com.de

Mehrtastenwörterbuch in c #?

Ich weiß, dass es keine in der BCL gibt, aber kann mich jemand auf eine gute Open Source-Quelle verweisen?

Mit Multi meine ich 2 Schlüssel. ;-)

102

Ich habe auch Tupel als Jason in seiner Antwort verwendet. Ich schlage jedoch vor, dass Sie einen Tuple einfach als Struktur definieren:

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); 
    }
}

Unveränderlichkeit, .GetHashcode und .Equals, erhalten Sie kostenlos, was (während Sie auf C # 4.0 warten) Nice 'n simple ist ...

Eine warning jedoch: Die Standardimplementierung GetHashcode (manchmal) berücksichtigt nur das erste Feld Stellen Sie also sicher, dass das erste Feld am diskriminierendsten ist, oder implementieren Sie GetHashcode selbst (z. B. mit FieldwiseHasher.Hash(this) from ValueUtils ). Andernfalls stoßen Sie wahrscheinlich auf Probleme mit der Skalierbarkeit.

Außerdem vermeiden Sie Nullen, die die Sache komplizierter machen (und wenn Sie wirklich Nullen wollen, machen Sie einfach Ihren Tuple<> nullfähig). Etwas offtopic, bin ich der einzige, der auf der Ebene des Frameworks keine Unterstützung für nicht-null-Referenzen hat? Ich arbeite an großen Projekten, und gelegentlich schleicht sich ein Nullpunkt an einem Ort ein, an dem es eigentlich nicht sein sollte - und hey, Sie erhalten eine Nullreferenzausnahme -, aber mit einer Stack-Ablaufverfolgung, die Sie auf die erste Verwendung der Referenz verweist, nicht auf den tatsächlich fehlerhaften Code .

Natürlich ist .NET 4.0 mittlerweile ziemlich alt. Die meisten von uns können nur das Tuple von .NET 4.0 verwenden.

Edit: zur Umgehung der schlechten GetHashCode-Implementierung, die .NET für von mir geschriebene Strukturen ValueUtils bereitstellt, wodurch Sie auch echte Namen für Ihre Mehrfeldschlüssel verwenden können; das bedeutet, dass Sie etwas schreiben könnten wie:

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

... was hoffentlich es einfacher macht, menschenlesbare Namen für Daten mit Wertesemantik zu haben, zumindest bis einige zukünftige Versionen von C # implementieren richtige Tupel mit benannten Mitgliedern ; hoffentlich mit anständigen Hashcodes ;-).

64
Eamon Nerbonne

Ich benutze eine Tuple als Schlüssel in einer Dictionary.

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

    // implementation details
}

Achten Sie darauf, Equals und GetHashCode zu überschreiben und entsprechend operator!= und operator== zu definieren. Sie können die Variable Tuple erweitern, um nach Bedarf weitere Elemente aufzunehmen. .NET 4.0 enthält eine integrierte Tuple.

53
jason

Tupel werden (sind) in .Net 4.0 Bis dahin können Sie auch ein 

 Dictionary<key1, Dictionary<key2, TypeObject>> 

oder erstellen Sie eine benutzerdefinierte Auflistungsklasse, um dies darzustellen ...

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

oder mit drei Schlüsseln ...

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

Viele gute Lösungen hier.. Was mir fehlt, ist eine Implementierung, die auf dem Build in Tuple basiert, also habe ich selbst eine geschrieben.

Da es nur von Dictionary<Tuple<T1,T2>, T> erbt, können Sie immer beide Möglichkeiten verwenden.

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));

hier ist der 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));
    }
}

Bitte beachten Sie, dass diese Implementierung von der Implementierung von Tuple.Equals () selbst abhängt:

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

Der obj-Parameter wird unter den folgenden Bedingungen als der aktuellen Instanz gleichgestellt: 

  • Es ist ein Tuple-Objekt. 
  • Seine zwei Komponenten sind vom selben Typ wie die aktuelle Instanz. 
  • Seine beiden Komponenten sind mit denen der aktuellen Instanz identisch. Die Gleichheit wird durch den Standardvergleich für Objektgleichheit für jede Komponente bestimmt.
16

Ich habe das geschrieben und habe es mit Erfolg benutzt.

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

Zur Zeit werden die Schlüssel einfach als Workaround in einer einzelnen Zeichenfolge verkettet. Dies funktioniert natürlich nicht für Nicht-String-Schlüssel. Ich würde gerne die Antwort wissen.

6
Adrian Godong

Werfen Sie einen Blick auf Wintellects PowerCollections ( CodePlex download ). Ich denke, ihr MultiDictionary tut so etwas. 

Es handelt sich um ein Wörterbuch mit Wörterbüchern. Sie haben also zwei Schlüssel, um auf jedes Objekt zuzugreifen, den Schlüssel für das Hauptwörterbuch, um das erforderliche Unterwörterbuch zu erhalten, und dann den zweiten Schlüssel für das Unterwörterbuch, um das erforderliche Element zu erhalten. Meinst Du das?

6
Simon P Stevens

Ich verwende das oft, weil es kurz ist und den syntaktischen Zucker liefert, den ich brauche ...

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;
        }
    }
}

Um es zu benutzen:

dict[cat][fish] = 9000;

wo die "Cat" -Taste auch nicht existieren muss.

6
max

Stimmt etwas nicht? 

neues Dictionary <KeyValuePair <Objekt, Objekt>, Objekt>
?

6
JSBձոգչ

Ich habe für diesen einen gegoogelt: http://www.codeproject.com/KB/recipes/multikey-dictionary.aspx . Ich denke, es ist das Hauptmerkmal im Vergleich zur Verwendung von struct, um 2 Schlüssel im regulären Wörterbuch zu enthalten: Sie können später von einem der Schlüssel referenzieren, anstatt 2 Schlüssel angeben zu müssen.

4
Marcin Deptuła

Wenn jemand nach einer ToMultiKeyDictionary() sucht, ist hier eine Implementierung, die mit den meisten Antworten funktionieren sollte (basierend auf Hermans ):

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

Ich denke, du würdest eine Tuple2-Klasse brauchen. Stellen Sie sicher, dass GetHashCode () und Equals () auf den zwei enthaltenen Elementen basieren.

Siehe Tupel in C #

2
Paul Ruane

Könnten Sie einen Dictionary<TKey1,Dictionary<TKey2,TValue>> verwenden?

Sie könnten dies sogar subclassieren:

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

EDIT: Dies ist jetzt eine doppelte Antwort. Es ist auch in seiner praktischen Anwendbarkeit begrenzt. Während es "funktioniert" und die Möglichkeit bietet, dict[key1][key2] zu codieren, gibt es viele "Problemumgehungen", damit es "nur funktionieren" kann.

HOWEVER: Nur für Kicks könnte man Dictionary trotzdem implementieren, aber an dieser Stelle wird es ein wenig ausführlich:

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

Hier ist ein konkretes Beispiel für eine Paarklasse, die als Schlüssel für ein Wörterbuch verwendet werden kann.

  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

Hier ist meine Implementierung. Ich wollte etwas, um die Umsetzung des Tuple-Konzepts zu verbergen.

  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);
    }
  }

Es hilft, die Verwendung wie ein Wörterbuch aussehen zu lassen

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

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

Hier ist ein weiteres Beispiel, in dem die Tuple-Klasse mit dem Dictionary verwendet wird.

        // 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