C# 复合密钥字典

C# 复合密钥字典,c#,dictionary,C#,Dictionary,我在列表中有一些对象,比如说List,MyClass有几个属性。我想根据MyClass的3个属性创建列表的索引。在这种情况下,两个属性是int,一个属性是datetime 基本上,我希望能够做到以下几点: Dictionary< CompositeKey , MyClass > MyClassListIndex = Dictionary< CompositeKey , MyClass >(); //Populate dictionary with items from t

我在列表中有一些对象,比如说
List
,MyClass有几个属性。我想根据MyClass的3个属性创建列表的索引。在这种情况下,两个属性是int,一个属性是datetime

基本上,我希望能够做到以下几点:

Dictionary< CompositeKey , MyClass > MyClassListIndex = Dictionary< CompositeKey , MyClass >();
//Populate dictionary with items from the List<MyClass> MyClassList
MyClass aMyClass = Dicitonary[(keyTripletHere)];
DictionaryMyClassListIndex=Dictionary();
//使用列表MyClassList中的项填充字典
MyClass=双丝分裂体[(键三重醚)];

有时,我会在一个列表上创建多个字典来索引它所包含的类的不同属性。但我不确定如何最好地处理复合键。我考虑过对三个值进行校验和,但这会有冲突的风险。

您可以将它们存储在结构中,并将其用作键:

struct CompositeKey
{
  public int value1;
  public int value2;
  public DateTime value3;
}
获取哈希代码的链接:

两种方法立刻浮现在脑海中:

  • 按照Kevin的建议去做,写一个结构作为你的密钥。确保此结构实现
    IEquatable
    ,并重写其
    Equals
    GetHashCode
    方法*

  • 编写一个内部使用嵌套字典的类。类似于:
    TripleKeyDictionary
    。。。该类内部将有一个类型为
    Dictionary
    的成员,并将公开
    this[TKey1 k1,TKey2 k2,TKey3 k3]
    包含skeys(TKey1 k1,TKey2 k2,TKey3 k3)
    等方法

  • *关于是否需要重写
    Equals
    方法的说明:虽然结构的
    Equals
    方法默认情况下比较每个成员的值是正确的,它是通过使用反射来实现的,反射固有地会带来性能成本,因此不是一个非常合适的实现,用于字典中的键(无论如何,在我看来)。根据MSDN文件,关于:

    的默认实现 Equals方法使用反射来 比较 obj和这个例子。覆盖 指定类型的等于方法 提高方法的性能 更紧密地代表了这个概念 类型的相等性


    我能想到的最好方法是创建一个CompositeKey结构,并确保
    覆盖GetHashCode()和Equals()方法,以确保使用集合时的速度和准确性:

    class Program
    {
        static void Main(string[] args)
        {
            DateTime firstTimestamp = DateTime.Now;
            DateTime secondTimestamp = firstTimestamp.AddDays(1);
    
            /* begin composite key dictionary populate */
            Dictionary<CompositeKey, string> compositeKeyDictionary = new Dictionary<CompositeKey, string>();
    
            CompositeKey compositeKey1 = new CompositeKey();
            compositeKey1.Int1 = 11;
            compositeKey1.Int2 = 304;
            compositeKey1.DateTime = firstTimestamp;
    
            compositeKeyDictionary[compositeKey1] = "FirstObject";
    
            CompositeKey compositeKey2 = new CompositeKey();
            compositeKey2.Int1 = 12;
            compositeKey2.Int2 = 9852;
            compositeKey2.DateTime = secondTimestamp;
    
            compositeKeyDictionary[compositeKey2] = "SecondObject";
            /* end composite key dictionary populate */
    
            /* begin composite key dictionary lookup */
            CompositeKey compositeKeyLookup1 = new CompositeKey();
            compositeKeyLookup1.Int1 = 11;
            compositeKeyLookup1.Int2 = 304;
            compositeKeyLookup1.DateTime = firstTimestamp;
    
            Console.Out.WriteLine(compositeKeyDictionary[compositeKeyLookup1]);
    
            CompositeKey compositeKeyLookup2 = new CompositeKey();
            compositeKeyLookup2.Int1 = 12;
            compositeKeyLookup2.Int2 = 9852;
            compositeKeyLookup2.DateTime = secondTimestamp;
    
            Console.Out.WriteLine(compositeKeyDictionary[compositeKeyLookup2]);
            /* end composite key dictionary lookup */
        }
    
        struct CompositeKey
        {
            public int Int1 { get; set; }
            public int Int2 { get; set; }
            public DateTime DateTime { get; set; }
    
            public override int GetHashCode()
            {
                return Int1.GetHashCode() ^ Int2.GetHashCode() ^ DateTime.GetHashCode();
            }
    
            public override bool Equals(object obj)
            {
                if (obj is CompositeKey)
                {
                    CompositeKey compositeKey = (CompositeKey)obj;
    
                    return ((this.Int1 == compositeKey.Int1) &&
                            (this.Int2 == compositeKey.Int2) &&
                            (this.DateTime == compositeKey.DateTime));
                }
    
                return false;
            }
        }
    }
    
    类程序
    {
    静态void Main(字符串[]参数)
    {
    DateTime firstTimestamp=DateTime.Now;
    DateTime secondTimestamp=firstTimestamp.AddDays(1);
    /*开始复合密钥字典填充*/
    Dictionary compositeKeyDictionary=新字典();
    CompositeKey CompositeKey 1=新CompositeKey();
    compositeKey1.Int1=11;
    compositeKey1.Int2=304;
    compositeKey1.DateTime=第一个时间戳;
    compositeKeyDictionary[compositeKey1]=“FirstObject”;
    CompositeKey CompositeKey 2=新CompositeKey();
    compositeKey2.Int1=12;
    compositeKey2.Int2=9852;
    compositeKey2.DateTime=第二个时间戳;
    compositeKeyDictionary[compositeKey2]=“SecondObject”;
    /*结束复合键字典填充*/
    /*开始复合键字典查找*/
    CompositeKey compositeKeyLookup1=新CompositeKey();
    compositeKeyLookup1.Int1=11;
    compositeKeyLookup1.Int2=304;
    compositeKeyLookup1.DateTime=第一个时间戳;
    Console.Out.WriteLine(compositeKeyDictionary[compositeKeyLookup1]);
    CompositeKey compositeKeyLookup2=新CompositeKey();
    compositeKeyLookup2.Int1=12;
    compositeKeyLookup2.Int2=9852;
    compositeKeyLookup2.DateTime=secondTimestamp;
    Console.Out.WriteLine(compositeKeyDictionary[compositeKeyLookup2]);
    /*结束组合键字典查找*/
    }
    结构复合键
    {
    公共int Int1{get;set;}
    公共int Int2{get;set;}
    公共日期时间日期时间{get;set;}
    公共覆盖int GetHashCode()
    {
    返回Int1.GetHashCode()^Int2.GetHashCode()^DateTime.GetHashCode();
    }
    公共覆盖布尔等于(对象对象对象)
    {
    如果(对象是复合键)
    {
    CompositeKey CompositeKey=(CompositeKey)obj;
    返回((this.Int1==compositeKey.Int1)&&
    (this.Int2==compositeKey.Int2)&&
    (this.DateTime==compositeKey.DateTime));
    }
    返回false;
    }
    }
    }
    
    关于GetHashCode()的MSDN文章:


    上述解决方案的另一个解决方案是存储迄今为止生成的所有键的某种列表,当生成新对象时,生成它的哈希代码(作为起点),检查它是否已经在列表中,如果已经在列表中,然后向其添加一些随机值等,直到获得唯一的键,然后将该键存储在对象本身和列表中,并始终将其作为键返回。

    如何使用
    字典

    这将允许您执行以下操作:

    MyClass item = MyData[8][23923][date];
    

    你应该使用元组。它们相当于CompositeKey类,但Equals()和GetHashCode()已经为您实现了

    var myClassIndex = new Dictionary<Tuple<int, bool, string>, MyClass>();
    //Populate dictionary with items from the List<MyClass> MyClassList
    foreach (var myObj in myClassList)
        myClassIndex.Add(Tuple.Create(myObj.MyInt, myObj.MyBool, myObj.MyString), myObj);
    MyClass myObj = myClassIndex[Tuple.Create(4, true, "t")];
    
    除非您需要自定义散列的计算,否则使用元组更简单

    如果要在复合键中包含许多属性,则元组类型名称可能会变得相当长,但可以通过创建自己的从元组派生的类来缩短名称


    **2017年编辑**

    有一个从C#7开始的新选项:值元组。想法相同,但语法不同,更轻:

    类型
    元组
    变为
    (int、bool、string)
    ,值变为
    
    
    var myClassIndex = myClassList.ToDictionary(myObj => Tuple.Create(myObj.MyInt, myObj.MyBool, myObj.MyString));
    MyClass myObj = myClassIndex[Tuple.Create(4, true, "t")];
    
        using System.Collections.ObjectModel;
    
        namespace IntIntKeyedCollection
        {
            class Program
            {
                static void Main(string[] args)
                {
                    Int32Int32DateO iid1 = new Int32Int32DateO(0, 1, new DateTime(2007, 6, 1, 8, 30, 52));
                    Int32Int32DateO iid2 = new Int32Int32DateO(0, 1, new DateTime(2007, 6, 1, 8, 30, 52));
                    if (iid1 == iid2) Console.WriteLine("same");
                    if (iid1.Equals(iid2)) Console.WriteLine("equals");
                    // that are equal but not the same I don't override = so I have both features
    
                    Int32Int32DateCollection int32Int32DateCollection = new Int32Int32DateCollection();
                    // dont't have to repeat the key like Dictionary
                    int32Int32DateCollection.Add(new Int32Int32DateO(0, 0, new DateTime(2008, 5, 1, 8, 30, 52)));
                    int32Int32DateCollection.Add(new Int32Int32DateO(0, 1, new DateTime(2008, 6, 1, 8, 30, 52)));
                    int32Int32DateCollection.Add(iid1);
                    //this would thow a duplicate key error
                    //int32Int32DateCollection.Add(iid2);
                    //this would thow a duplicate key error
                    //int32Int32DateCollection.Add(new Int32Int32DateO(0, 1, new DateTime(2008, 6, 1, 8, 30, 52)));
                    Console.WriteLine("count");
                    Console.WriteLine(int32Int32DateCollection.Count.ToString());
                    // reference by ordinal postion (note the is not the long key)
                    Console.WriteLine("oridinal");
                    Console.WriteLine(int32Int32DateCollection[0].GetHashCode().ToString());
                    // reference by index
                    Console.WriteLine("index");
                    Console.WriteLine(int32Int32DateCollection[0, 1, new DateTime(2008, 6, 1, 8, 30, 52)].GetHashCode().ToString());
                    Console.WriteLine("foreach");
                    foreach (Int32Int32DateO iio in int32Int32DateCollection)
                    {
                        Console.WriteLine(string.Format("HashCode {0} Int1 {1} Int2 {2} DateTime {3}", iio.GetHashCode(), iio.Int1, iio.Int2, iio.Date1));
                    }
                    Console.WriteLine("sorted by date");
                    foreach (Int32Int32DateO iio in int32Int32DateCollection.OrderBy(x => x.Date1).ThenBy(x => x.Int1).ThenBy(x => x.Int2))
                    {
                        Console.WriteLine(string.Format("HashCode {0} Int1 {1} Int2 {2} DateTime {3}", iio.GetHashCode(), iio.Int1, iio.Int2, iio.Date1));
                    }
                    Console.ReadLine();
                }
                public class Int32Int32DateCollection : KeyedCollection<Int32Int32DateS, Int32Int32DateO>
                {
                    // This parameterless constructor calls the base class constructor 
                    // that specifies a dictionary threshold of 0, so that the internal 
                    // dictionary is created as soon as an item is added to the  
                    // collection. 
                    // 
                    public Int32Int32DateCollection() : base(null, 0) { }
    
                    // This is the only method that absolutely must be overridden, 
                    // because without it the KeyedCollection cannot extract the 
                    // keys from the items.  
                    // 
                    protected override Int32Int32DateS GetKeyForItem(Int32Int32DateO item)
                    {
                        // In this example, the key is the part number. 
                        return item.Int32Int32Date;
                    }
    
                    //  indexer 
                    public Int32Int32DateO this[Int32 Int1, Int32 Int2, DateTime Date1]
                    {
                        get { return this[new Int32Int32DateS(Int1, Int2, Date1)]; }
                    }
                }
    
                public struct Int32Int32DateS
                {   // required as KeyCollection Key must be a single item
                    // but you don't really need to interact with Int32Int32DateS directly
                    public readonly Int32 Int1, Int2;
                    public readonly DateTime Date1;
                    public Int32Int32DateS(Int32 int1, Int32 int2, DateTime date1)
                    { this.Int1 = int1; this.Int2 = int2; this.Date1 = date1; }
                }
                public class Int32Int32DateO : Object
                {
                    // implement other properties
                    public Int32Int32DateS Int32Int32Date { get; private set; }
                    public Int32 Int1 { get { return Int32Int32Date.Int1; } }
                    public Int32 Int2 { get { return Int32Int32Date.Int2; } }
                    public DateTime Date1 { get { return Int32Int32Date.Date1; } }
    
                    public override bool Equals(Object obj)
                    {
                        //Check for null and compare run-time types.
                        if (obj == null || !(obj is Int32Int32DateO)) return false;
                        Int32Int32DateO item = (Int32Int32DateO)obj;
                        return (this.Int32Int32Date.Int1 == item.Int32Int32Date.Int1 &&
                                this.Int32Int32Date.Int2 == item.Int32Int32Date.Int2 &&
                                this.Int32Int32Date.Date1 == item.Int32Int32Date.Date1);
                    }
                    public override int GetHashCode()
                    {
                        return (((Int64)Int32Int32Date.Int1 << 32) + Int32Int32Date.Int2).GetHashCode() ^ Int32Int32Date.GetHashCode();
                    }
                    public Int32Int32DateO(Int32 Int1, Int32 Int2, DateTime Date1)
                    {
                        Int32Int32DateS int32Int32Date = new Int32Int32DateS(Int1, Int2, Date1);
                        this.Int32Int32Date = int32Int32Date;
                    }
                }
            }
        }
    
    var dictionary = new Dictionary<object, string> ();
    dictionary[new { a = 1, b = 2 }] = "value";
    
    // declare:
    Dictionary<(string, string, int), MyClass> index;
    
    // populate:
    foreach (var m in myClassList) {
      index[(m.Name, m.Path, m.JobId)] = m;
    }
    
    // retrieve:
    var aMyClass = index[("foo", "bar", 15)];
    
    // Perf from https://gist.github.com/ljw1004/61bc96700d0b03c17cf83dbb51437a69
    //
    //              Tuple ValueTuple KeyValuePair
    //  Allocation:  160   100        110
    //    Argument:   75    80         80    
    //      Return:   75   210        210
    //        Load:  160   170        320
    // GetHashCode:  820   420       2700
    //      Equals:  280   470       6800