元组(或数组)作为C#中的字典键

Ale*_*exH 104 c# dictionary tuples hashtable

我想在C#中创建一个Dictionary查找表.我需要将一个3元组的值解析为一个字符串.我尝试使用数组作为键,但这不起作用,我不知道还能做什么.在这一点上,我正在考虑制作一本词典字典词典,但这看起来可能不是很漂亮,尽管我会在javascript中这样做.

Hal*_*rim 107

如果您使用的是.NET 4.0,请使用Tuple:

lookup = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();
Run Code Online (Sandbox Code Playgroud)

如果没有,您可以定义一个元组并将其用作关键字.元组需要覆盖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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 你的`GetHashCode`实现不是很好.它在场的排列下是不变的. (29认同)
  • @jerryjvl和谷歌发现这个像我一样的所有人,.NET 4的Tuple [实现等于](http://msdn.microsoft.com/en-us/library/dd270346.aspx)所以它可以用于字典. (14认同)
  • 该结构还应该实现IEquatable <Tuple <T,U,W >>.这样,在哈希码冲突的情况下调用Equals()时可以避免装箱. (6认同)
  • @Thoraot - 当然你的例子是假的...它应该是.为什么`new object()`等于另一个`new object()`?它不只是使用直接引用comarison ...尝试:`bool test = new Tuple <int,string>(1,"foo").Equals(new Tuple <int,string>(1,"Foo".ToLower( )));` (5认同)
  • 元组不应该是一个结构体。在框架中,Tuple 是一种引用类型。 (2认同)
  • @Theraot总之,如果所有嵌套类型都有有用的.Equals方法,则元组可以用作字典键.例如`new Tuple <int,Tuple <int,int >>(1,new Tuple <int,int>(2,3)).Equals(new Tuple <int,Tuple <int,int >>(1,new Tuple) <int,int>(2,3)))` (2认同)

naw*_*fal 34

在基于元组和嵌套字典的方法之间,基于元组的几乎总是更好.

从可维护性的角度来看,

  • 它更容易实现看起来像这样的功能:

    var myDict = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();
    
    Run Code Online (Sandbox Code Playgroud)

    var myDict = new Dictionary<TypeA, Dictionary<TypeB, Dictionary<TypeC, string>>>();
    
    Run Code Online (Sandbox Code Playgroud)

    来自被叫方.在第二种情况下,每个添加,查找,删除等都需要对多个字典执行操作.

  • 此外,如果您的复合键将来需要一个(或更少)字段,则需要在第二种情况下更改代码(嵌套字典),因为您必须添加更多嵌套字典和后续检查.

从性能角度来看,您可以达到的最佳结论是自己测量.但是您可以事先考虑一些理论上的限制:

  • 在嵌套字典的情况下,为每个键(外部和内部)添加一个额外的字典会产生一些内存开销(超过创建元组所具有的内容).

  • 在嵌套字典中,每个基本操作(如添加,更新,查找,删除等)都需要在两个字典中执行.现在有一种情况,嵌套字典方法可以更快,即,当查找的数据不存在时,因为中间字典可以绕过完整的哈希代码计算和比较,但是再次应该确定时间.在数据存在的情况下,它应该更慢,因为查找应该执行两次(或者三次,具体取决于嵌套).

  • 关于元组方法,.NET元组在它们被用作集合中的键时不是最高效的,因为它EqualsGetHashCode实现导致值类型的装箱.

我会选择基于元组的字典,但如果我想要更多的性能,我会使用自己的元组更好的实现.


另一方面,很少有化妆品可以使词典变得很酷:

  1. 索引器样式调用可以更加清晰和直观.例如,

    string foo = dict[a, b, c]; //lookup
    dict[a, b, c] = ""; //update/insertion
    
    Run Code Online (Sandbox Code Playgroud)

    因此,在您的字典类中公开必要的索引器,在内部处理插入和查找.

  2. 此外,实现一个合适的IEnumerable接口,并提供一个Add(TypeA, TypeB, TypeC, string)方法,它将为您提供集合初始化程序语法,如:

    new MultiKeyDictionary<TypeA, TypeB, TypeC, string> 
    { 
        { a, b, c, null }, 
        ...
    };
    
    Run Code Online (Sandbox Code Playgroud)


gab*_*bba 12

良好,干净,快速,简单和可读的方式是:

  • 为当前类型生成等于成员(Equals()和GetHashCode())方法.像ReSharper这样的工具不仅可以创建方法,还可以生成相等性检查和/或计算哈希码的必要代码.生成的代码将比Tuple实现更优化.
  • 只需创建一个从元组派生的简单密钥类.

添加类似这样的东西:

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;
}
Run Code Online (Sandbox Code Playgroud)

所以你可以将它与字典一起使用:

var myDictinaryData = new Dictionary<myKey, string>()
{
    {new myKey(1, 2, 3), "data123"},
    {new myKey(4, 5, 6), "data456"},
    {new myKey(7, 8, 9), "data789"}
};
Run Code Online (Sandbox Code Playgroud)
  • 您也可以在合同中使用它
  • 作为linq中加入或分组的关键
  • 这样你永远不会错误地输入Item1,Item2,Item3的顺序......
  • 你不需要记住或研究代码来了解去哪里获得某些东西
  • 不需要重写IStructuralEquatable,IStructuralComparable,IComparable,ITuple他们都在这里alredy


Dou*_*las 11

如果您使用的是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();
Run Code Online (Sandbox Code Playgroud)


jer*_*jvl 7

如果由于某种原因你真的想避免创建自己的Tuple类,或者使用内置于.NET 4.0中,还有另一种方法可行; 您可以将三个键值组合成一个值.

例如,如果这三个值是整数类型,而不是超过64位,则可以将它们组合成一个ulong.

最坏的情况是你总是可以使用一个字符串,只要你确保其中的三个组件用一些字符或序列分隔,这些字符或序列不会出现在键的组件内,例如,你可以尝试三个数字:

string.Format("{0}#{1}#{2}", key1, key2, key3)
Run Code Online (Sandbox Code Playgroud)

这种方法显然有一些构成开销,但取决于你使用它的方法,这可能是微不足道的,不关心它.

  • 虽然这**会起作用,但它的代码味道很差. (17认同)
  • 我会说这很大程度上取决于背景; 如果我有三种整数类型要结合,并且性能并不重要,那么这种方法完全正常,错误的可能性很小.当然,从.NET 4开始,所有这些都是完全冗余的,因为微软将为我们提供开箱即用的(大概是正确的!)Tuple类型. (6认同)
  • 如果任何键(“ key1”,“ key2”,“ key3”)是包含分界符(“#”`)的字符串,则可能会造成混乱。 (2认同)