it-swarm.dev

C#中的多键字典?

我知道BCL中没有一个,但有人能指出我一个好的开源吗?

By Multi我指的是2把钥匙。 ;-)

104
SuperSuperDev1234

我也使用元组作为 jason的答案 的确如此。但是,我建议您只需将Tuple定义为结构:

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

你可以免费获得不变性,.GetHashcode.Equals,(当你在等待C#4.0时)很简单......

一个 警告 但是:默认GetHashcode实现(有时) 只考虑第一个字段 所以确保使第一个字段最具辨别力或自己实现GetHashcode(例如使用FieldwiseHasher.Hash(this) from ValueUtils ),否则你可能会遇到可扩展性问题。

此外,您可以避免使用往往使问题复杂化的空值(如果您确实需要空值,则只需使Tuple<>可为空)。稍微偏离主题,我是唯一一个对框架级别缺乏对非空引用的支持而烦恼的人吗?我在大型项目上工作,偶尔会在某个地方出现一个无法实现的小问题 - 而且嘿嘿,你得到一个空引用异常 - 但是有一个堆栈跟踪指向引用的第一个用法,而不是实际有缺陷的代码。

当然,.NET 4.0现在已经很老了;我们大多数人都可以使用.NET 4.0的元组。

编辑:解决.NET为我编写的结构提供的糟糕的GetHashCode实现 ValueUtils ,它还允许您为多字段键使用实名;这意味着你可能会写下这样的东西:

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

...希望能够更容易地为具有值语义的数据提供人类可读的名称,至少在 某些未来版本的C#实现具有命名成员的正确元组 ;希望有合适的哈希码;-)。

64
Eamon Nerbonne

我使用Tuple作为Dictionary中的键。

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

    // implementation details
}

一定要覆盖 EqualsGetHashCode 并根据需要定义operator!=operator==。您可以根据需要展开Tuple来保存更多项目。 .NET 4.0将包含一个内置的Tuple

54
jason

元组将在.Net 4.0中。在此之前,你也可以使用

 Dictionary<key1, Dictionary<key2, TypeObject>> 

或者,创建一个自定义集合类来表示这个...

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

或者,有三把钥匙......

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

这里有很多好的解决方案,我在这里缺少的是一个基于Tuple类型构建的实现,所以我自己写了一个。

由于它只是继承自Dictionary<Tuple<T1,T2>, T>,因此您可以始终使用两种方式。

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

这是代码。

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

请注意,此实现取决于Tuple.Equals()实现本身:

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

在以下条件下,obj参数被视为等于当前实例:

  • 它是一个元组对象。
  • 它的两个组件与当前实例的类型相同。
  • 它的两个组件与当前实例的组件相同。等式由每个组件的默认对象相等比较器确定。
18
Jürgen Steinblock

我写了并成功地使用了它。

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
Herman Schoenfeld

我目前只是简单地将键连接成一个字符串作为解决方法。当然,这不适用于非字符串键。很想知道答案。

6
Adrian Godong

看一下Wintellect的 PowerCollectionsCodePlex下载 )。我认为他们的MultiDictionary就是这样的。

这是一本字典词典,所以你有2个键可以访问每个对象,主词典的键可以获得所需的子词典,然后是子词典的第二个键,可以获得所需的项目。你是这个意思吗?

6
Simon P Stevens

我经常使用它,因为它很短,并提供我需要的语法糖...

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

要使用它:

dict[cat][fish] = 9000;

其中“Cat”键也不一定存在。

6
max

有什么不对吗?

new Dictionary <KeyValuePair <object,object>,object>
6
JSBձոգչ

我用Google搜索了这个: http://www.codeproject.com/KB/recipes/multikey-dictionary.aspx 。我想这与使用struct在常规字典中包含2个键相比的主要特征是,您可以稍后通过其中一个键引用,而不必提供2个键。

4
Marcin Deptuła

如果有人正在寻找ToMultiKeyDictionary(),这里的实现应该适用于大多数答案(基于 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

我想你需要一个类似Tuple2的课程。确保它是GetHashCode()和Equals()基于两个包含的元素。

参见 C#中的元组 /

2
Paul Ruane

你能用Dictionary<TKey1,Dictionary<TKey2,TValue>>吗?

你甚至可以将其子类化:

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

编辑: 现在这是一个重复的答案。它的实用性也受到限制。虽然它确实“工作”并提供了编码dict[key1][key2]的能力,但是有很多“变通办法”才能让它“正常工作”。

但是: 只是为了踢,但是仍然可以实现Dictionary,但是在这一点上它有点冗长:

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

这是一个对称类的充实示例,可以用作Dictionary的关键字。

  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

这是我的实施。我想要隐藏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);
    }
  }

它有助于保持使用看起来像字典

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

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

这是使用Tuple类和Dictionary的另一个例子。

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