พจนานุกรมคีย์คอมโพสิต


91

ฉันมีวัตถุบางอย่างในรายการสมมติว่าList<MyClass>MyClass มีคุณสมบัติหลายอย่าง ฉันต้องการสร้างดัชนีของรายการตามคุณสมบัติ 3 ประการของ MyClass ในกรณีนี้ 2 ของคุณสมบัติคือ int และคุณสมบัติหนึ่งคือวันที่และเวลา

โดยทั่วไปฉันต้องการที่จะทำสิ่งต่างๆเช่น:

Dictionary< CompositeKey , MyClass > MyClassListIndex = Dictionary< CompositeKey , MyClass >();
//Populate dictionary with items from the List<MyClass> MyClassList
MyClass aMyClass = Dicitonary[(keyTripletHere)];

บางครั้งฉันสร้างพจนานุกรมหลายรายการในรายการเพื่อจัดทำดัชนีคุณสมบัติต่างๆของคลาสที่มี ฉันไม่แน่ใจว่าจะจัดการกับคีย์คอมโพสิตได้ดีที่สุดแค่ไหน ฉันถือว่าทำการตรวจสอบค่าสามค่า แต่จะเสี่ยงต่อการชนกัน


2
ทำไมคุณไม่ใช้ Tuples? พวกเขาทำทุกอย่างเพื่อคุณ
Eldritch Conundrum

21
ฉันไม่รู้ว่าจะตอบสนองต่อสิ่งนั้นอย่างไร คุณถามคำถามนั้นราวกับว่าคุณคิดว่าฉันจงใจหลีกเลี่ยงสิ่งที่เกิดขึ้น
AaronLS

6
ขออภัยฉันเขียนใหม่เป็นคำตอบที่ละเอียดกว่านี้
Eldritch Conundrum

1
ก่อนที่จะใช้ชั้นเองอ่านเกี่ยวกับ Tuple (แนะนำโดย Eldritch ปริศนา) - msdn.microsoft.com/en-us/library/system.tuple.aspx เปลี่ยนได้ง่ายกว่าและช่วยให้คุณประหยัดการสร้างคลาสแบบกำหนดเอง
OSH

คำตอบ:


108

คุณควรใช้ tuples เทียบเท่ากับคลาส 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")];

หรือใช้ System.Linq

var myClassIndex = myClassList.ToDictionary(myObj => Tuple.Create(myObj.MyInt, myObj.MyBool, myObj.MyString));
MyClass myObj = myClassIndex[Tuple.Create(4, true, "t")];

หากคุณไม่จำเป็นต้องปรับแต่งการคำนวณของแฮชการใช้ทูเปิลจะง่ายกว่า

หากมีคุณสมบัติจำนวนมากที่คุณต้องการรวมไว้ในคีย์ผสมชื่อประเภท Tuple อาจค่อนข้างยาว แต่คุณสามารถทำให้ชื่อสั้นลงได้โดยการสร้างคลาสของคุณเองซึ่งได้มาจาก Tuple <... >


** แก้ไขเมื่อปี 2560 **

มีตัวเลือกใหม่ที่เริ่มต้นด้วย C # 7: ค่าสิ่งที่เพิ่มขึ้น แนวคิดเหมือนกัน แต่ไวยากรณ์ต่างกันเบากว่า:

ชนิดTuple<int, bool, string>กลายเป็น(int, bool, string)และค่าที่จะกลายเป็นTuple.Create(4, true, "t")(4, true, "t")

ด้วยค่า tuples คุณสามารถตั้งชื่อองค์ประกอบได้ด้วย โปรดทราบว่าการแสดงมีความแตกต่างกันเล็กน้อยดังนั้นคุณอาจต้องทำการเปรียบเทียบหากสิ่งเหล่านี้มีความสำคัญสำหรับคุณ


4
Tuple ไม่ใช่ตัวเลือกที่ดีสำหรับคีย์เนื่องจากสร้างการชนกันของแฮชจำนวนมาก stackoverflow.com/questions/12657348/…
paparazzo

1
@Blam KeyValuePair<K,V>และโครงสร้างอื่น ๆ มีฟังก์ชันแฮชเริ่มต้นที่ทราบว่าไม่ดี (ดูรายละเอียดเพิ่มเติมได้ที่stackoverflow.com/questions/3841602/… ) Tuple<>อย่างไรก็ตามไม่ใช่ ValueType และฟังก์ชันแฮชเริ่มต้นอย่างน้อยก็จะใช้ฟิลด์ทั้งหมด ดังที่กล่าวไว้หากปัญหาหลักของโค้ดของคุณคือการชนกันให้ใช้การปรับGetHashCode()ให้เหมาะสมกับข้อมูลของคุณ
Eldritch Conundrum

1
แม้ว่า Tuple จะไม่ใช่ ValueType จากการทดสอบของฉัน แต่ก็ต้องทนทุกข์ทรมานจากการชนกันมาก
paparazzo

5
ฉันคิดว่าคำตอบนี้ล้าสมัยแล้วที่เรามี ValueTuples มีไวยากรณ์ที่ดีกว่าใน C # และดูเหมือนว่า GetHashCode จะเร็วกว่า Tuples ถึงสองเท่า - gist.github.com/ljw1004/61bc96700d0b03c17cf83dbb51437a69
Lucian Wischik

3
@LucianWischik ขอบคุณฉันได้อัปเดตคำตอบเพื่อพูดถึงพวกเขา
Eldritch Conundrum

22

วิธีที่ดีที่สุดที่ฉันคิดได้คือการสร้างโครงสร้าง 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;
        }
    }
}

บทความ MSDN เกี่ยวกับ GetHashCode ():

http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx


ฉันไม่คิดว่าเป็นจริงแน่นอน 100% ที่จะเป็นแฮชโค้ดที่ไม่ซ้ำใครมีโอกาสมาก
Hans Olsson

นั่นอาจเป็นเรื่องจริง! ตามบทความ MSDN ที่เชื่อมโยงนั่นคือวิธีที่แนะนำในการแทนที่ GetHashCode () อย่างไรก็ตามเนื่องจากฉันไม่ได้ใช้คีย์คอมโพสิตมากนักในการทำงานประจำวันฉันจึงไม่สามารถพูดได้อย่างแน่นอน
Allen E.Scharfenberg

4
ใช่. หากคุณแยกชิ้นส่วน Dictionary.FindEntry () ด้วยตัวสะท้อนแสงคุณจะเห็นว่ามีการทดสอบทั้งแฮชโค้ดและความเท่าเทียมกันทั้งหมด แฮชโค้ดจะถูกทดสอบก่อนและหากล้มเหลวจะทำการลัดวงจรเงื่อนไขโดยไม่ตรวจสอบความเท่าเทียมกันทั้งหมด หากแฮชผ่านไปจะมีการทดสอบความเท่าเทียมกันด้วย
Jason Kleban

1
และใช่ควรลบล้างเท่ากับเพื่อให้ตรงกันด้วย แม้ว่าคุณจะทำให้ GetHashCode () ส่งคืน 0 สำหรับอินสแตนซ์ใดก็ตาม Dictionary ก็ยังใช้งานได้ แต่ก็ช้าลง
Jason Kleban

2
ประเภททูเปิลในตัวใช้แฮชผสมเป็น '(h1 << 5) + h1 ^ h2' แทน 'h1 ^ h2' ของคุณ ฉันเดาว่าพวกเขาทำเช่นนั้นเพื่อหลีกเลี่ยงการชนกันทุกครั้งที่วัตถุทั้งสองที่จะแฮชมีค่าเท่ากัน
Eldritch Conundrum

13

แล้วไงDictionary<int, Dictionary<int, Dictionary<DateTime, MyClass>>>?

สิ่งนี้จะช่วยให้คุณทำ:

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

1
สิ่งนี้จะสร้างวัตถุจำนวนมากขึ้นจากนั้นใช้โครงสร้างหรือคลาส CompositeKey และจะช้าลงเนื่องจากจะใช้การค้นหาสองระดับ
Ian Ringrose

ฉันเชื่อว่ามันเป็นการเปรียบเทียบจำนวนเท่ากัน - ฉันไม่เห็นว่าจะมีออบเจ็กต์อื่น ๆ อีกมากมายได้อย่างไร - วิธีคีย์คอมโพสิตยังคงต้องการคีย์และเป็นค่าคอมโพเนนต์หรืออ็อบเจกต์และอีกหนึ่งคำสั่งที่จะถือ วิธีที่ซ้อนกันนี้คุณไม่จำเป็นต้องมีคีย์ Wrapper สำหรับแต่ละออบเจ็กต์ / ค่าโดยมีคำสั่งเพิ่มเติมหนึ่งรายการสำหรับระดับรังเพิ่มเติมแต่ละระดับ คุณคิดอย่างไร?
Jason Kleban

9
จากการเปรียบเทียบของฉันซึ่งฉันได้ลองใช้คีย์ที่มี 2 และ 3 ส่วน: โซลูชันพจนานุกรมแบบซ้อนนั้นเร็วกว่าการใช้คีย์คอมโพสิตทูเพิล 3-4 เท่า อย่างไรก็ตามวิธีทูเพิลนั้นง่ายกว่า / เป็นระเบียบกว่ามาก
RickL

5
@RickL ฉันสามารถยืนยันเกณฑ์มาตรฐานเหล่านั้นได้เราใช้ประเภทในฐานรหัสของเราที่เรียกว่าCompositeDictionary<TKey1, TKey2, TValue>(ฯลฯ ) ซึ่งสืบทอดมาจากDictionary<TKey1, Dictionary<TKey2, TValue>>(หรือจำเป็นต้องใช้พจนานุกรมที่ซ้อนกันจำนวนมากก็ตามโดยไม่ต้องใช้ทั้งประเภทตั้งแต่ต้น (แทนที่จะโกงโดยใช้ พจนานุกรมหรือประเภทที่ซ้อนกันเพื่อให้มีคีย์) นี่คือวิธีที่เร็วที่สุดที่เราได้รับ
Adam Houldsworth

1
วิธีการสร้างคำสั่งแบบซ้อนควรเร็วกว่าสำหรับกรณีที่ไม่มีข้อมูล (?) เพียงครึ่งเดียวเนื่องจากพจนานุกรมกลางสามารถข้ามการคำนวณและเปรียบเทียบรหัสแฮชแบบเต็มได้ เมื่อมีข้อมูลควรทำงานช้าลงเนื่องจากควรดำเนินการขั้นพื้นฐานเช่นเพิ่มประกอบด้วย ฯลฯ สามครั้ง ฉันแน่ใจว่าระยะขอบด้วยวิธีทูเพิลถูกเอาชนะในเกณฑ์มาตรฐานที่กล่าวถึงข้างต้นเกี่ยวกับรายละเอียดการใช้งานของ. ทริป
เปิลที่

12

คุณสามารถจัดเก็บไว้ในโครงสร้างและใช้เป็นคีย์:

struct CompositeKey
{
  public int value1;
  public int value2;
  public DateTime value3;
}

ลิงก์เพื่อรับรหัสแฮช: http://msdn.microsoft.com/en-us/library/system.valuetype.gethashcode.aspx


ฉันติด. NET 3.5 ดังนั้นฉันจึงไม่สามารถเข้าถึงTuples ได้ดังนั้นนี่จึงเป็นทางออกที่ดี!
aarona

ฉันประหลาดใจที่ไม่ได้รับการโหวตเพิ่มขึ้น เป็นวิธีง่ายๆที่อ่านได้ง่ายกว่า Tuple
มาร์ค

1
ตาม msdn สิ่งนี้ทำงานได้ดีหากไม่มีฟิลด์ใดเป็นประเภทการอ้างอิงมิฉะนั้นจะใช้การสะท้อนเพื่อความเท่าเทียมกัน
Gregor Slavec

@Mark ปัญหาของโครงสร้างคือการใช้ GetHashCode () ที่เป็นค่าเริ่มต้นไม่รับประกันว่าจะใช้ฟิลด์ทั้งหมดของโครงสร้าง (นำไปสู่ประสิทธิภาพของพจนานุกรมที่ไม่ดี) ในขณะที่ Tuple เสนอการรับประกันดังกล่าว ฉันได้ทดสอบแล้ว ดูstackoverflow.com/questions/3841602/…สำหรับรายละเอียดที่เต็มไปด้วยเลือด
Eldritch Conundrum

8

ตอนนี้ VS2017 / C # 7 ออกมาแล้วคำตอบที่ดีที่สุดคือการใช้ ValueTuple:

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

ฉันเลือกที่จะประกาศพจนานุกรมที่มีการระบุชื่อ (string, string, int)ValueTuple แต่ฉันสามารถตั้งชื่อให้พวกเขา(string name, string path, int id)ได้

Perfwise ที่ ValueTuple ใหม่จะเร็วกว่า Tuple ที่แต่ช้ากว่าที่GetHashCode Equalsฉันคิดว่าคุณจะต้องทำการทดลองแบบ end-to-end ให้เสร็จสมบูรณ์เพื่อดูว่าวิธีใดเร็วที่สุดสำหรับสถานการณ์ของคุณ แต่ความสวยงามและไวยากรณ์แบบ end-to-end สำหรับ ValueTuple ทำให้ชนะ

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

ใช่ฉันผ่านการเขียนซ้ำครั้งใหญ่เพื่อให้โซลูชัน Anonymous Type ระเบิดขึ้นในใบหน้าของฉัน (ไม่สามารถเปรียบเทียบประเภทที่ไม่ระบุตัวตนที่สร้างขึ้นด้วยชุดประกอบที่แตกต่างกันได้) ValueTuple ดูเหมือนจะเป็นวิธีแก้ปัญหาที่ค่อนข้างหรูหราสำหรับปัญหาของคีย์พจนานุกรมแบบผสม
Quarkly

5

สองแนวทางที่เกิดขึ้นในใจทันที:

  1. ทำตามที่เควินแนะนำและเขียนโครงสร้างที่จะใช้เป็นกุญแจของคุณ อย่าลืมทำให้โครงสร้างนี้ใช้งานได้IEquatable<TKey>และเพื่อแทนที่โครงสร้างEqualsและGetHashCodeวิธีการ *

  2. เขียนคลาสที่ใช้พจนานุกรมซ้อนภายใน สิ่งที่ชอบ: TripleKeyDictionary<TKey1, TKey2, TKey3, TValue>... ชั้นนี้ภายในจะต้องเป็นสมาชิกของประเภทDictionary<TKey1, Dictionary<TKey2, Dictionary<TKey3, TValue>>>และจะเปิดเผยวิธีการเช่นthis[TKey1 k1, TKey2 k2, TKey3 k3], ContainsKeys(TKey1 k1, TKey2 k2, TKey3 k3)ฯลฯ

* คำที่ระบุว่าการลบล้างEqualsเมธอดนั้นจำเป็นหรือไม่: ในขณะที่Equalsวิธีการสำหรับโครงสร้างจะเปรียบเทียบมูลค่าของสมาชิกแต่ละคนโดยค่าเริ่มต้นจะเป็นเช่นนั้นโดยใช้การสะท้อน - ซึ่งส่งผลต่อต้นทุนด้านประสิทธิภาพโดยเนื้อแท้ - ดังนั้นจึงไม่มาก การใช้งานที่เหมาะสมสำหรับสิ่งที่ตั้งใจจะใช้เป็นคีย์ในพจนานุกรม (ในความคิดของฉันอยู่แล้ว) ตามเอกสาร MSDN เกี่ยวกับValueType.Equals:

การดำเนินการตามค่าเริ่มต้นของเมธอด Equals จะใช้การสะท้อนเพื่อเปรียบเทียบฟิลด์ที่เกี่ยวข้องของ obj และอินสแตนซ์นี้ แทนที่เมธอด Equals สำหรับประเภทเฉพาะเพื่อปรับปรุงประสิทธิภาพของเมธอดและแสดงแนวคิดเกี่ยวกับความเท่าเทียมกันสำหรับประเภทอย่างใกล้ชิดยิ่งขึ้น


เกี่ยวกับ 1 ฉันไม่คิดว่า yuo จำเป็นต้องแทนที่ Equals และ GetHashcode การใช้ Equals เริ่มต้นจะตรวจสอบความเท่าเทียมกันโดยอัตโนมัติในทุกฟิลด์ซึ่งฉันคิดว่าควรจะใช้ได้กับโครงสร้างนี้
Hans Olsson

@ho: อาจไม่จำเป็นแต่ฉันขอแนะนำอย่างยิ่งให้ทำเช่นนั้นสำหรับโครงสร้างใด ๆ ที่จะทำหน้าที่เป็นกุญแจสำคัญ ดูการแก้ไขของฉัน
ด่านเต๋า

3

KeyedCollectionถ้าคีย์เป็นส่วนหนึ่งของการเรียนแล้วใช้
เป็นDictionaryที่มาของคีย์จากวัตถุ
ภายใต้ครอบคลุมมันเป็นพจนานุกรม
ไม่ได้มีการทำซ้ำที่สำคัญในและKey ทำไมต้องใช้โอกาสที่คีย์ไม่เหมือนกันในรูปแบบ. ไม่ต้องทำซ้ำข้อมูลเดียวกันในหน่วยความจำ Value
KeyValue

คลาส KeyedCollection

Indexer เพื่อแสดงคีย์คอมโพสิต

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

สำหรับการใช้ประเภทค่า fpr คีย์ที่ Microsoft แนะนำโดยเฉพาะ

ValueType.GetHashCode

Tuple ในทางเทคนิคไม่ใช่ประเภทค่า แต่มีอาการเดียวกัน (การชนกันของแฮช) และไม่ใช่ตัวเลือกที่ดีสำหรับคีย์


+1 สำหรับคำตอบที่ถูกต้องมากขึ้น ไม่แปลกใจที่ไม่มีใครพูดถึงก่อนหน้านี้ ในความเป็นจริงขึ้นอยู่กับว่า OP ตั้งใจจะใช้โครงสร้างอย่างไรHashSet<T>ด้วยความเหมาะสมIEqualityComparer<T>ก็เป็นตัวเลือก Btw ฉันคิดว่าคำตอบของคุณจะดึงดูดคะแนนถ้าคุณสามารถเปลี่ยนชื่อชั้นเรียนและชื่อสมาชิกอื่น ๆ :)
nawfal

2

ฉันขอแนะนำทางเลือก - วัตถุที่ไม่ระบุตัวตน เหมือนกับที่เราใช้ในเมธอด GroupBy LINQ ที่มีหลายปุ่ม

var dictionary = new Dictionary<object, string> ();
dictionary[new { a = 1, b = 2 }] = "value";

มันอาจจะดูแปลก ๆ แต่ฉันได้เปรียบเทียบ Tuple.GetHashCode และเมธอด {a = 1, b = 2} .GetHashCode ใหม่และอ็อบเจ็กต์ที่ไม่ระบุตัวตนชนะบนเครื่องของฉันบน.

วัตถุ - 89,1732 ms สำหรับการโทร 10,000 ครั้งใน 1,000 รอบ

Tuple - 738,4475 ms สำหรับการโทร 10,000 ครั้งใน 1,000 รอบ


โอ้ทางเลือกนี้ไม่เคยอยู่ในความคิดของฉัน ... ฉันไม่รู้ว่ามันจะทำงานได้ดีหรือไม่ถ้าคุณใช้คีย์คอมโพสิตเป็นคีย์ผสม
Gabriel Espinoza

หากคุณส่งผ่านวัตถุ (แทนที่จะเป็นวัตถุที่ไม่ระบุชื่อ) ผลลัพธ์ของเมธอด GetHashCode ของวัตถุนี้จะถูกใช้ หากคุณใช้มันเช่นdictionary[new { a = my_obj, b = 2 }]นั้นรหัสแฮชที่ได้จะเป็นการรวมกันของ my_obj.GetHashCode และ ((Int32) 2) .GetHashCode
Michael Logutov

อย่าใช้วิธีนี้! แอสเซมบลีที่แตกต่างกันสร้างชื่อที่แตกต่างกันสำหรับชนิดที่ไม่ระบุชื่อ แม้ว่าคุณจะดูไม่ระบุตัวตน แต่เบื้องหลังมีคลาสที่เป็นรูปธรรมที่สร้างขึ้นและออบเจ็กต์สองคลาสที่แตกต่างกันสองคลาสจะไม่เท่ากันกับตัวดำเนินการเริ่มต้น
Quarkly

และกรณีนี้มีความสำคัญอย่างไร?
Michael Logutov

0

อีกวิธีหนึ่งสำหรับสิ่งที่กล่าวถึงแล้วคือการจัดเก็บรายการคีย์ทั้งหมดที่สร้างขึ้นจนถึงตอนนี้และเมื่อสร้างวัตถุใหม่ขึ้นคุณจะสร้างแฮชโค้ด (เช่นเดียวกับจุดเริ่มต้น) ตรวจสอบว่ามีอยู่ในรายการหรือไม่ คือจากนั้นเพิ่มค่าสุ่มและอื่น ๆ ลงไปจนกว่าคุณจะมีคีย์ที่ไม่ซ้ำกันจากนั้นเก็บคีย์นั้นไว้ในอ็อบเจ็กต์เองและในรายการและส่งคืนค่านั้นเป็นคีย์ตลอดเวลา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.