元组(或数组)作为 C# 中的字典键
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/955982/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Tuples( or arrays ) as Dictionary keys in C#
提问by AlexH
I am trying to make a Dictionary lookup table in C#. I need to resolve a 3-tuple of values to one string. I tried using arrays as keys, but that did not work, and I don't know what else to do. At this point I am considering making a Dictionary of Dictionaries of Dictionaries, but that would probably not be very pretty to look at, though it is how I would do it in javascript.
我正在尝试在 C# 中创建一个字典查找表。我需要将一个 3 元组的值解析为一个字符串。我尝试使用数组作为键,但这不起作用,我不知道还能做什么。在这一点上,我正在考虑制作一个字典字典,但这可能不太好看,尽管我会在 javascript 中这样做。
采纳答案by Hallgrim
If you are on .NET 4.0 use a Tuple:
如果您使用的是 .NET 4.0,请使用元组:
lookup = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();
If not you can define a Tuple and use that as the key. The Tuple needs to override GetHashCode, Equals and IEquatable:
如果没有,您可以定义一个元组并将其用作键。Tuple 需要覆盖 GetHashCode、Equals 和 IEquatable:
struct Tuple<T, U, W> : IEquatable<Tuple<T,U,W>>
{
readonly T first;
readonly U second;
readonly W third;
public Tuple(T first, U second, W third)
{
this.first = first;
this.second = second;
this.third = third;
}
public T First { get { return first; } }
public U Second { get { return second; } }
public W Third { get { return third; } }
public override int GetHashCode()
{
return first.GetHashCode() ^ second.GetHashCode() ^ third.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
return Equals((Tuple<T, U, W>)obj);
}
public bool Equals(Tuple<T, U, W> other)
{
return other.first.Equals(first) && other.second.Equals(second) && other.third.Equals(third);
}
}
回答by John Gietzen
I would override your Tuple with a proper GetHashCode, and just use it as the key.
我会用适当的 GetHashCode 覆盖您的元组,并将其用作键。
As long as you overload the proper methods, you should see decent performance.
只要你重载了正确的方法,你就会看到不错的性能。
回答by jerryjvl
If for some reason you really want to avoid creating your own Tuple class, or using on built into .NET 4.0, there is one other approach possible; you can combine the three key values together into a single value.
如果出于某种原因,您真的想避免创建自己的 Tuple 类,或使用内置于 .NET 4.0 中的方法,那么还有另一种方法可能;您可以将三个键值组合成一个值。
For example, if the three values are integer types together not taking more than 64 bits, you could combine them into a ulong
.
例如,如果这三个值是整数类型,总共不超过 64 位,您可以将它们组合成一个ulong
.
Worst-case you can always use a string, as long as you make sure the three components in it are delimited with some character or sequence that does not occur inside the components of the key, for example, with three numbers you could try:
在最坏的情况下,您始终可以使用字符串,只要您确保其中的三个组成部分由一些不会出现在键组成部分内的字符或序列分隔,例如,您可以尝试使用三个数字:
string.Format("{0}#{1}#{2}", key1, key2, key3)
There is obviously some composition overhead in this approach, but depending on what you are using it for this may be trivial enough not to care about it.
在这种方法中显然有一些组合开销,但取决于你使用它的目的,这可能是微不足道的,不需要关心它。
回答by Michael Graczyk
Here is the .NET tuple for reference:
这是 .NET 元组供参考:
[Serializable]
public class Tuple<T1, T2, T3> : IStructuralEquatable, IStructuralComparable, IComparable, ITuple {
private readonly T1 m_Item1;
private readonly T2 m_Item2;
private readonly T3 m_Item3;
public T1 Item1 { get { return m_Item1; } }
public T2 Item2 { get { return m_Item2; } }
public T3 Item3 { get { return m_Item3; } }
public Tuple(T1 item1, T2 item2, T3 item3) {
m_Item1 = item1;
m_Item2 = item2;
m_Item3 = item3;
}
public override Boolean Equals(Object obj) {
return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);;
}
Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
if (other == null) return false;
Tuple<T1, T2, T3> objTuple = other as Tuple<T1, T2, T3>;
if (objTuple == null) {
return false;
}
return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2) && comparer.Equals(m_Item3, objTuple.m_Item3);
}
Int32 IComparable.CompareTo(Object obj) {
return ((IStructuralComparable) this).CompareTo(obj, Comparer<Object>.Default);
}
Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer) {
if (other == null) return 1;
Tuple<T1, T2, T3> objTuple = other as Tuple<T1, T2, T3>;
if (objTuple == null) {
throw new ArgumentException(Environment.GetResourceString("ArgumentException_TupleIncorrectType", this.GetType().ToString()), "other");
}
int c = 0;
c = comparer.Compare(m_Item1, objTuple.m_Item1);
if (c != 0) return c;
c = comparer.Compare(m_Item2, objTuple.m_Item2);
if (c != 0) return c;
return comparer.Compare(m_Item3, objTuple.m_Item3);
}
public override int GetHashCode() {
return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
}
Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item1), comparer.GetHashCode(m_Item2), comparer.GetHashCode(m_Item3));
}
Int32 ITuple.GetHashCode(IEqualityComparer comparer) {
return ((IStructuralEquatable) this).GetHashCode(comparer);
}
public override string ToString() {
StringBuilder sb = new StringBuilder();
sb.Append("(");
return ((ITuple)this).ToString(sb);
}
string ITuple.ToString(StringBuilder sb) {
sb.Append(m_Item1);
sb.Append(", ");
sb.Append(m_Item2);
sb.Append(", ");
sb.Append(m_Item3);
sb.Append(")");
return sb.ToString();
}
int ITuple.Size {
get {
return 3;
}
}
}
回答by mungflesh
If your consuming code can make do with an IDictionary<> interface, instead of Dictionary, my instinct would have been to use a SortedDictionary<> with a custom array comparer, ie:
如果您的消费代码可以使用 IDictionary<> 接口而不是 Dictionary,那么我的直觉是将 SortedDictionary<> 与自定义数组比较器一起使用,即:
class ArrayComparer<T> : IComparer<IList<T>>
where T : IComparable<T>
{
public int Compare(IList<T> x, IList<T> y)
{
int compare = 0;
for (int n = 0; n < x.Count && n < y.Count; ++n)
{
compare = x[n].CompareTo(y[n]);
}
return compare;
}
}
And create thus (using int[] just for concrete example's sake):
并因此创建(使用 int[] 只是为了具体示例):
var dictionary = new SortedDictionary<int[], string>(new ArrayComparer<int>());
回答by nawfal
Between tuple and nested dictionaries based approaches, it's almost always better to go for tuple based.
在基于元组和嵌套字典的方法之间,基于元组的方法几乎总是更好。
From maintainability point of view,
从可维护性的角度来看,
its much easier to implement a functionality that looks like:
var myDict = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();
than
var myDict = new Dictionary<TypeA, Dictionary<TypeB, Dictionary<TypeC, string>>>();
from the callee side. In the second case each addition, lookup, removal etc require action on more than one dictionary.
Furthermore, if your composite key require one more (or less) field in future, you will need to change code a significant lot in the second case (nested dictionary) since you have to add further nested dictionaries and subsequent checks.
实现如下功能要容易得多:
var myDict = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();
比
var myDict = new Dictionary<TypeA, Dictionary<TypeB, Dictionary<TypeC, string>>>();
从被叫方。在第二种情况下,每次添加、查找、删除等都需要对多个字典进行操作。
此外,如果您的复合键将来需要一个更多(或更少)的字段,您将需要在第二种情况(嵌套字典)中大量更改代码,因为您必须添加更多嵌套字典和后续检查。
From performance perspective, the best conclusion you can reach is by measuring it yourself. But there are a few theoretical limitations which you can consider beforehand:
从性能的角度来看,您可以得出的最佳结论是自己进行测量。但是,您可以事先考虑一些理论限制:
In the nested dictionary case, having an additional dictionary for every keys (outer and inner) will have some memory overhead (more than what creating a tuple would have).
In the nested dictionary case, every basic action like addition, updation, lookup, removal etc need to be carried out in two dictionaries. Now there is a case where nested dictionary approach can be faster, i.e., when the data being looked up is absent, since the intermediate dictionaries can bypass the full hash code computation & comparison, but then again it should be timed to be sure. In presence of data, it should be slower since lookups should be performed twice (or thrice depending on nesting).
Regarding tuple approach, .NET tuples are not the most performant when they're meant to be used as keys in sets since its
Equals
andGetHashCode
implementation causes boxing for value types.
在嵌套字典的情况下,为每个键(外部和内部)拥有一个额外的字典将有一些内存开销(比创建元组所具有的更多)。
在嵌套字典的情况下,添加、更新、查找、删除等每个基本操作都需要在两个字典中执行。现在有一种情况,嵌套字典方法可以更快,即,当正在查找的数据不存在时,因为中间字典可以绕过完整的哈希码计算和比较,但是应该再次确定时间。在存在数据的情况下,它应该更慢,因为查找应该执行两次(或三次,取决于嵌套)。
关于元组的方法,.NET元组是不是最高效的,当他们命中注定要被用作自钥匙套
Equals
和GetHashCode
实施的原因拳击值类型。
I would go with tuple based dictionary, but if I want more performance, I would use my own tuple with better implementation.
我会使用基于元组的字典,但是如果我想要更高的性能,我会使用我自己的元组来实现更好的实现。
On a side note, few cosmetics can make the dictionary cool:
附带说明一下,很少有化妆品可以使字典变酷:
Indexer style calls can be a lot cleaner and intuitive. For eg,
string foo = dict[a, b, c]; //lookup dict[a, b, c] = ""; //update/insertion
So expose necessary indexers in your dictionary class which internally handles the insertions and lookups.
Also, implement a suitable
IEnumerable
interface and provide anAdd(TypeA, TypeB, TypeC, string)
method which would give you collection initializer syntax, like:new MultiKeyDictionary<TypeA, TypeB, TypeC, string> { { a, b, c, null }, ... };
索引器样式调用可以更加简洁和直观。例如,
string foo = dict[a, b, c]; //lookup dict[a, b, c] = ""; //update/insertion
因此,在内部处理插入和查找的字典类中公开必要的索引器。
此外,实现一个合适的
IEnumerable
接口并提供一种Add(TypeA, TypeB, TypeC, string)
方法,该方法将为您提供集合初始值设定项语法,例如:new MultiKeyDictionary<TypeA, TypeB, TypeC, string> { { a, b, c, null }, ... };
回答by gabba
The good, clean, fast, easy and readable ways is:
好的、干净、快速、简单和可读的方式是:
- generate equality members (Equals() and GetHashCode())method for the current type. Tools like ReSharpernot only creates the methods, but also generates the necessary code for an equality check and/or for calculating hash code. The generated code will be more optimal than Tuple realization.
- just make a simple key class derived from a tuple.
- 为当前类型生成相等成员(Equals() 和 GetHashCode())方法。像ReSharper这样的工具不仅可以创建方法,还可以为相等性检查和/或计算哈希码生成必要的代码。生成的代码将比元组实现更优化。
- 只需创建一个从元组派生的简单键类。
add something similar like this:
添加类似这样的东西:
public sealed class myKey : Tuple<TypeA, TypeB, TypeC>
{
public myKey(TypeA dataA, TypeB dataB, TypeC dataC) : base (dataA, dataB, dataC) { }
public TypeA DataA => Item1;
public TypeB DataB => Item2;
public TypeC DataC => Item3;
}
So you can use it with dictionary:
所以你可以将它与字典一起使用:
var myDictinaryData = new Dictionary<myKey, string>()
{
{new myKey(1, 2, 3), "data123"},
{new myKey(4, 5, 6), "data456"},
{new myKey(7, 8, 9), "data789"}
};
- You also can use it in contracts
- as a key for join or groupings in linq
- going this way you never ever mistype order of Item1, Item2, Item3 ...
- you no need to remember or look into to code to understand where to go to get something
- no need to override IStructuralEquatable, IStructuralComparable, IComparable, ITuple they all alredy here
- 您也可以在合同中使用它
- 作为 linq 中加入或分组的键
- 按照这种方式,您永远不会打错 Item1、Item2、Item3 的顺序……
- 您无需记住或查看代码即可了解去哪里获取某些东西
- 无需覆盖 IStructuralEquatable、IStructuralComparable、IComparable、ITuple 他们都在这里
回答by Douglas
If you're on C# 7, you should consider using value tuples as your composite key. Value tuples typically offer better performance than the traditional reference tuples (Tuple<T1, …>
) since value tuples are value types (structs), not reference types, so they avoid the memory allocation and garbage collection costs. Also, they offer conciser and more intuitive syntax, allowing for their fields to be named if you so wish. They also implement the IEquatable<T>
interface needed for the dictionary.
如果您使用的是 C# 7,则应考虑使用值元组作为复合键。值元组通常比传统的引用元组 ( Tuple<T1, …>
)提供更好的性能,因为值元组是值类型(结构),而不是引用类型,因此它们避免了内存分配和垃圾收集成本。此外,它们提供更简洁、更直观的语法,允许您根据需要命名其字段。它们还实现IEquatable<T>
了字典所需的接口。
var dict = new Dictionary<(int PersonId, int LocationId, int SubjectId), string>();
dict.Add((3, 6, 9), "ABC");
dict.Add((PersonId: 4, LocationId: 9, SubjectId: 10), "XYZ");
var personIds = dict.Keys.Select(k => k.PersonId).Distinct().ToList();