c#中的多键字典? [复制]
Posted
技术标签:
【中文标题】c#中的多键字典? [复制]【英文标题】:Multi-key dictionary in c#? [duplicate] 【发布时间】:2010-11-13 09:30:49 【问题描述】:我知道 BCL 中没有一个,但谁能指出一个好的开源代码?
Multi 我的意思是 2 个键。 ;-)
【问题讨论】:
您想要一个由多个属性组成的键,还是希望它可以使同一个键可以在同一个字典中多次存在?这些是不同的。 ***.com/questions/689940/… 您可能想添加一个示例用法,以阐明您的意思。 这些值得一看:noocyte.wordpress.com/2008/02/18/double-key-dictionary 和 codeproject.com/Articles/32894/C-Multi-key-Generic-Dictionary 没有 +1 让问题变得如此简短和模糊。您的问题可以通过 10 种不同的方式暗示 【参考方案1】:我目前只是将键连接成一个字符串作为一种解决方法。当然,这不适用于非字符串键。也很想知道答案。
【讨论】:
因为在大多数类上 .ToSting() 对于所有值(即类型名称)都是相同的。并非所有类型都具有有效的字符串表示等。 那么,不能使用密封类作为键,而不是实现一个由多个元组组成的类,而是必须继承许多类。 是的,太多的 BCL 类没有实现 ToString,这意味着您不能假设 ToString 将返回给定类型状态的唯一表示。 TL;DR:失败 使用字符串连接可能很危险。例如,如果您有整数 123 和 456,并将它们连接起来创建一个键,这将给出“123456”。如果你有 12 和 3456 并将它们连接起来,你会得到......“123456”。哎呀。 @Meta-Knight 使用分隔符。呵呵。【参考方案2】:我使用Tuple
作为Dictionary
中的键。
public class Tuple<T1, T2>
public T1 Item1 get; private set;
public T2 Item2 get; private set;
// implementation details
确保覆盖 Equals
和 GetHashCode
并根据需要定义 operator!=
和 operator==
。您可以根据需要扩展Tuple
以容纳更多项目。 .NET 4.0 将包含一个内置的Tuple
。
【讨论】:
查看这篇关于多个“多键”字典实现之间的性能测试的博文here @AronW 据我所知,这篇博文从未使用Tuple
作为密钥?
如果你要编写自己的 Tuple 类,不妨使用命名项而不是 T1、T2...【参考方案3】:
元组将(是)在 .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>>>
【讨论】:
绝妙的答案——这非常适合我的需求。 这不是多键字典,这是一个非常糟糕的元组键字典实现,因为你不能通过第二个键访问值,没有它你也不能访问它们(除非你得到它由内部字典的值组成,但它可以包含多个项目)。此外,您的解决方案缺少用于添加和删除项目的代码。 天啊...我什至不确定您在说什么。您希望多键字典能够使用 either 键访问对象吗?这绝不等同于操作要求的元组。对你来说,你真的期望一个完全充实的专业实施,所有的花里胡哨,作为问题的答案?做梦吧,我为此得到报酬。编写自己的代码。【参考方案4】:看看 Wintellect 的 PowerCollections (CodePlex download)。我认为他们的 MultiDictionary 就是这样做的。
这是一个字典字典,所以你有 2 个键来访问每个对象,主字典的键为你获取所需的子字典,然后是子字典的第二个键以获取所需的项目。是这个意思吗?
【讨论】:
他们的MultiDictionary<,>
允许为单个键分配多个值,而不是在这个意义上有多个键。
但 OP 的问题尽可能模糊。因此 +1 表示不同的看法。
@pbalaga 是对的,PowerCollections 不允许您拥有两个键(因此被否决)【参考方案5】:
我认为你需要一个 Tuple2 类。确保它的 GetHashCode() 和 Equals() 是基于这两个包含的元素。
见Tuples in C#
【讨论】:
【参考方案6】:有什么问题吗
新字典?, object>
【讨论】:
有内置的 PairTuple
类。
是的,有一点不对:KVP使用ValueType.GetHashCode - "如果调用派生类型的GetHashCode方法,返回值不太可能适合用作哈希表中的键。 " KVP 对于 Dictionary 键来说是一个完全不合适的选择,并且可能会产生很多冲突。【参考方案7】:
我也像jason in his answer 那样使用元组。但是,我建议您将元组简单地定义为结构:
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
实现(有时)only considers the first field 所以请确保使第一个字段最具区分性或自己实现GetHashcode
(例如使用FieldwiseHasher.Hash(this)
from ValueUtils),否则您可能会遇到可扩展性问题。
此外,您可以避免使事情复杂化的空值(如果您真的想要空值,只需将 Tuple<>
设为可空值)。有点跑题了,我是唯一一个对框架级别缺乏对非空引用的支持感到恼火的人吗?我在大型项目上工作,偶尔会有一个空值出现在它确实不应该出现的地方——嘿,你会得到一个空引用异常——但是有一个堆栈跟踪可以指向引用的第一次使用,而不是实际错误的代码.
当然,.NET 4.0 现在已经很老了;我们大多数人都可以使用 .NET 4.0 的元组。
编辑: 解决 .NET 为我编写的 ValueUtils 的结构提供的糟糕的 GetHashCode
实现,它还允许您使用真实姓名您的多字段键;这意味着您可能会写如下内容:
sealed class MyValueObject : ValueObject<MyValueObject>
public DayOfWeek day;
public string NamedPart;
//properties work fine too
...这有望使具有值语义的数据更容易获得人类可读的名称,至少在 some future version of C# implements proper tuples with named members 之前;希望有不错的哈希码;-)。
【讨论】:
关于 .NET 中元组的引用与值类型决策的精彩介绍:msdn.microsoft.com/en-us/magazine/dd942829.aspx#id0400060 @nawfal:我决定重新实现哈希码函数生成器。有用;但它仍然很准系统:github.com/EamonNerbonne/ValueUtils @EamonNerbonne 看起来不错。我不太确定OverriddenHashCodeMethod
的工作。将不得不测试。
@nawfal:我会完善它并添加更多功能,然后将其发布到 nuget。如果您真的打算使用它,我很乐意提供有关如何使其适用于您的用例的反馈 - 请随时在 github 上发表评论!
@nawfal:好的,我认为它现在处于非常可用的状态。我很想听听您对两个具体设计问题的看法:github.com/EamonNerbonne/ValueUtils/issues/1 和 github.com/EamonNerbonne/ValueUtils/issues/2。即便如此,这已准备好实际使用 - 只需尝试引用 nuget:ValueUtils,然后创建您的类 ala sealed class MyClass : ValueObject<MyClass>
!早期的性能测试表明它比 ValueType 快得多,比 Tuple<>
快得多,并且在手动代码的 2 倍以内。【参考方案8】:
我用谷歌搜索了这个:http://www.codeproject.com/KB/recipes/multikey-dictionary.aspx。我想与使用 struct 在常规字典中包含 2 个键相比,它的主要特点是您以后可以通过其中一个键进行引用,而不必提供 2 个键。
【讨论】:
【参考方案9】:你能用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;
【讨论】:
我明白了,Charles 也在想同样的事情。然而,这些问题在于分配。这可能是一个难以管理的野兽。 +1 为命名,DualKey..一直在寻找一个好名字:)【参考方案10】:这是一个对类的充实示例,它可以用作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();
【讨论】:
【参考方案11】:我经常使用它,因为它很短并且提供了我需要的语法糖......
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”键也不必存在。
【讨论】:
我还应该提到,用另一个类dict[cat].ContainsKey(mouse)
如果 cat 不存在,你总是得到错误,因为它是新的。如果您经常以这种方式使用它,我会对其进行基准测试 - 这不会是一种非常优化的方式。
被低估的解决方案! (: 使用 4.0 之前的 .NET 时非常方便。【参考方案12】:
我编写并成功使用了它。
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);
【讨论】:
我为这个答案写了一个ToMultiKeyDictionary()
扩展和posted it as an answer below
这在元组不可用的 Unity 中效果很好!!!请注意,您需要using System.Linq;
【参考方案13】:
这是我的实现。我想要一些东西来隐藏元组概念的实现。
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"];
【讨论】:
如何遍历所有?【参考方案14】:如果有人正在寻找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;
【讨论】:
【参考方案15】:这里有很多好的解决方案,
我这里缺少的是基于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参数在以下情况下被认为等于当前实例:
它是一个元组对象。 它的两个组件与当前实例的类型相同。 它的两个组件与当前实例的组件相同。相等性由每个组件的默认对象相等性比较器确定。【讨论】:
简单好用,谢谢。 非常好的解决方案【参考方案16】:这是另一个将 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)];
【讨论】:
以上是关于c#中的多键字典? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
c#多键字典,用自己的可变类替换元组时出现“KeyNotFoundException”[重复]