एक सामान्य शब्दकोश कुंजी के रूप में एक वस्तु के क्षेत्र का उपयोग करना


120

यदि मैं वस्तुओं को कुंजी के रूप में उपयोग करना चाहता हूं Dictionary, तो उन्हें विशिष्ट तरीके से तुलना करने के लिए मुझे किन तरीकों की आवश्यकता होगी?

कहो कि मेरे पास एक वर्ग है जिसमें गुण हैं:

class Foo {
    public string Name { get; set; }
    public int FooID { get; set; }

    // elided
} 

और मैं एक बनाना चाहते हैं:

Dictionary<Foo, List<Stuff>>

मैं चाहता हूं कि समान समूह के Fooसाथ वस्तुओं को समान FooIDमाना जाए। Fooकक्षा में ओवरराइड करने के लिए मुझे किन तरीकों की आवश्यकता होगी ?

संक्षेप में: मैं Stuffवस्तुओं को सूचियों में वर्गीकृत करना चाहता हूं , Fooवस्तुओं द्वारा वर्गीकृत । Stuffवस्तुओं FooIDको उनकी श्रेणी से जोड़ने के लिए एक होगा ।

जवाबों:


151

डिफ़ॉल्ट रूप से, दो महत्वपूर्ण विधियां हैं GetHashCode()और Equals()। यह महत्वपूर्ण है कि अगर दो चीजें समान हैं (यह Equals()सच है), कि उनके पास एक ही हैश-कोड है। उदाहरण के लिए, आप "FooID लौटा सकते हैं;" के रूप में GetHashCode()अगर आपको लगता है कि मैच के रूप में चाहते हैं। आप भी लागू कर सकते हैं IEquatable<Foo>, लेकिन यह वैकल्पिक है:

class Foo : IEquatable<Foo> {
    public string Name { get; set;}
    public int FooID {get; set;}

    public override int GetHashCode() {
        return FooID;
    }
    public override bool Equals(object obj) {
        return Equals(obj as Foo);
    }
    public bool Equals(Foo obj) {
        return obj != null && obj.FooID == this.FooID;
    }
}

अंत में, एक और विकल्प के लिए एक IEqualityComparer<T>ही प्रदान करना है।


5
+1 और मेरा मतलब इस धागे को हाइजैक करने का नहीं है, लेकिन मैं इस धारणा के तहत था कि गेटहैशकोड () को FooId.GetHashCode () लौटना चाहिए। क्या यह सही पैटर्न नहीं है?
केन ब्राउनिंग

8
@ - अच्छी तरह से, यह सिर्फ एक int लौटने की जरूरत है जो आवश्यक सुविधाएँ प्रदान करता है। जो FooID FooID.GetHashCode () के साथ ही करेगा। कार्यान्वयन विवरण के रूप में, Int32.GetHashCode () "इसे वापस लौटाएं"। अन्य प्रकारों (स्ट्रिंग आदि) के लिए, फिर हाँ: .GetHashCode () बहुत उपयोगी होगा।
मार्क Gravell

2
धन्यवाद! मैं IEqualityComparer <T> के साथ चला गया क्योंकि यह केवल डिकैरियोनरी के लिए था जिसे मुझे ओवरराइड विधियों की आवश्यकता थी।
दाना

1
आपको पता होना चाहिए कि हैशटैब (डिक्शनरी <T>, डिक्शनरी, हैशटेबल, आदि) पर आधारित कंटेनरों का प्रदर्शन उपयोग किए गए हैश फ़ंक्शन की गुणवत्ता पर निर्भर करता है। यदि आप बस हैश कोड के रूप में FooID करते हैं, तो कंटेनर बहुत खराब प्रदर्शन कर सकते हैं।
जोर्जेन फॉग

2
@ JrrgenFogh मुझे इस बात की बहुत जानकारी है; प्रस्तुत उदाहरण बताए गए इरादे के अनुरूप है। हैश अपरिवर्तनीयता से संबंधित बहुत सी संबंधित चिंताएं हैं; आईडी नाम की तुलना में कम बार बदलते हैं, और आमतौर पर मज़बूती से अद्वितीय और तुल्यता के संकेतक हैं। एक गैर तुच्छ विषय, यद्यपि।
मार्क Gravell

33

जैसा कि आप FooIDसमूह के लिए पहचानकर्ता बनना चाहते हैं , आपको फू वस्तु के बजाय शब्दकोश में कुंजी का उपयोग करना चाहिए:

Dictionary<int, List<Stuff>>

यदि आप Fooऑब्जेक्ट को कुंजी के रूप में उपयोग करते हैं , तो आप केवल संपत्ति पर विचार करने के लिए GetHashCodeऔर Equalsविधि को लागू करेंगे FooIDNameसंपत्ति सिर्फ मृत वजन जहाँ तक हो जाएगा Dictionaryचिंतित था, तो आप सिर्फ प्रयोग करेंगे Fooएक के लिए एक आवरण के रूप में int

इसलिए FooIDसीधे मूल्य का उपयोग करना बेहतर है , और फिर आपको कुछ भी लागू करने की आवश्यकता नहीं है क्योंकि कुंजी के रूप में Dictionaryपहले से ही समर्थन करता है int

संपादित करें:
यदि आप Fooवैसे भी कुंजी के रूप में कक्षा का उपयोग करना चाहते हैं , तो IEqualityComparer<Foo>इसे लागू करना आसान है:

public class FooEqualityComparer : IEqualityComparer<Foo> {
   public int GetHashCode(Foo foo) { return foo.FooID.GetHashCode(); }
   public bool Equals(Foo foo1, Foo foo2) { return foo1.FooID == foo2.FooID; }
}

उपयोग:

Dictionary<Foo, List<Stuff>> dict = new Dictionary<Foo, List<Stuff>>(new FooEqualityComparer());

1
अधिक सही ढंग से, int पहले से ही एक कुंजी के रूप में उपयोग करने के लिए आवश्यक विधियों / इंटरफेस का समर्थन करता है। शब्दकोश को इंट या किसी अन्य प्रकार का कोई प्रत्यक्ष ज्ञान नहीं है।
जिम मेंथल

मैंने इसके बारे में सोचा, लेकिन कई कारणों से यह क्लीनर और ऑब्जेक्ट की कुंजीशब्दों के रूप में उपयोग करने के लिए अधिक सुविधाजनक था।
दाना

1
ठीक है, यह केवल ऐसा लगता है कि आप ऑब्जेक्ट को कुंजी के रूप में उपयोग कर रहे हैं, क्योंकि आप वास्तव में केवल आईडी के रूप में कुंजी का उपयोग कर रहे हैं।
गुफ़ा

8

Foo के लिए आपको ऑब्जेक्ट को ओवरराइड करना होगा। GetHashCode () और object.Equals ()

शब्दकोश, GetHashCode () को प्रत्येक मूल्य के लिए एक हैश बाल्टी की गणना करने और दो फू के समान होने की तुलना करने के लिए बराबर करेगा।

अच्छे हैश कोड की गणना करना सुनिश्चित करें (समान हैशकोड वाले कई समान फू वस्तुओं से बचें), लेकिन सुनिश्चित करें कि दो समान फ़ॉल्स में समान हैश कोड है। आप बराबरी-विधि से शुरू करना चाहते हैं और फिर (GetHashCode () में) हर सदस्य की बराबरी के हैश कोड को बराबर करते हैं।

public class Foo { 
     public string A;
     public string B;

     override bool Equals(object other) {
          var otherFoo = other as Foo;
          if (otherFoo == null)
             return false;
          return A==otherFoo.A && B ==otherFoo.B;
     }

     override int GetHashCode() {
          return 17 * A.GetHashCode() + B.GetHashCode();
     }
}

2
एक तरफ - लेकिन एक्सोर (^) हैश-कोड के लिए एक खराब कॉम्बिनेटर बनाता है, क्योंकि यह अक्सर बहुत अधिक विकर्ण टकराव (यानी {"फू", "बार"} बनाम {"बार", "फू"} को बेहतर बनाता है। ; विकल्प गुणा और प्रत्येक शब्द को जोड़ने के लिए है - यानी 17 * a.GetHashCode () + B.GetHashCode ()
मार्क Gravell

2
मार्क, मैं देख रहा हूं कि आपका क्या मतलब है। लेकिन आप जादू नंबर 17 में कैसे आते हैं? क्या हैश के संयोजन के लिए एक गुणक के रूप में अभाज्य संख्या का उपयोग करना फायदेमंद है? यदि हां, तो क्यों?
20

क्या मैं लौटने का सुझाव दे सकता हूं: (A + B) .GetHashCode () इसके बजाय: 17 * A.GetHashCode () + B.GetHashCode () यह होगा: 1) टक्कर होने की संभावना कम है और 2) सुनिश्चित करें कि कोई भी नहीं है पूर्णांक अतिप्रवाह।
चार्ल्स बर्न्स 22

(A + B) .GetHashCode () बहुत खराब हैशिंग एल्गोरिथ्म के लिए बनाता है, क्योंकि (ए, बी) के विभिन्न सेट एक ही हैश में परिणाम कर सकते हैं यदि वे एक ही स्ट्रिंग के लिए समवर्ती होते हैं; "नरक" + "ned" "नरक" + "स्वामित्व" के समान है और उसी हैश में परिणाम देगा।
कासेव २३'१४

@kaesve कैसे (A + "" + B) .GetHashCode () के बारे में?
टाइमलेस

1

Hashtableकक्षा के बारे में क्या !

Hashtable oMyDic = new Hashtable();
Object oAnyKeyObject = null;
Object oAnyValueObject = null;
oMyDic.Add(oAnyKeyObject, oAnyValueObject);
foreach (DictionaryEntry de in oMyDic)
{
   // Do your job
}

उपरोक्त तरीके से, आप किसी भी वस्तु (अपनी कक्षा की वस्तु) को एक सामान्य शब्दकोश कुंजी के रूप में उपयोग कर सकते हैं :)


1

मुझे भी यही समस्या थी। मैं अब समतुल्य और GetHashCode को ओवरराइड करने के कारण एक कुंजी के रूप में कोशिश की गई किसी भी वस्तु का उपयोग कर सकता हूं।

यहां एक वर्ग है जिसे मैंने इक्वाल्स (ऑब्जेक्ट ओब्ज) और गेटहैशकोड () के ओवरराइड्स के अंदर उपयोग करने के तरीकों के साथ बनाया है। मैंने जेनरिक और एक हैशिंग एल्गोरिथ्म का उपयोग करने का फैसला किया, जो कि अधिकांश वस्तुओं को कवर करने में सक्षम होना चाहिए। कृपया मुझे बताएं कि क्या आप यहां कुछ भी देखते हैं जो किसी प्रकार की वस्तु के लिए काम नहीं करता है और आपके पास इसे सुधारने का एक तरीका है।

public class Equality<T>
{
    public int GetHashCode(T classInstance)
    {
        List<FieldInfo> fields = GetFields();

        unchecked
        {
            int hash = 17;

            foreach (FieldInfo field in fields)
            {
                hash = hash * 397 + field.GetValue(classInstance).GetHashCode();
            }
            return hash;
        }
    }

    public bool Equals(T classInstance, object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }
        if (ReferenceEquals(this, obj))
        {
            return true;
        }
        if (classInstance.GetType() != obj.GetType())
        {
            return false;
        }

        return Equals(classInstance, (T)obj);
    }

    private bool Equals(T classInstance, T otherInstance)
    {
        List<FieldInfo> fields = GetFields();

        foreach (var field in fields)
        {
            if (!field.GetValue(classInstance).Equals(field.GetValue(otherInstance)))
            {
                return false;
            }
        }

        return true;
    }

    private List<FieldInfo> GetFields()
    {
        Type myType = typeof(T);

        List<FieldInfo> fields = myType.GetTypeInfo().DeclaredFields.ToList();
        return fields;
    }
}

यहां बताया गया है कि इसका उपयोग कक्षा में कैसे किया जाता है:

public override bool Equals(object obj)
    {
        return new Equality<ClassName>().Equals(this, obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return new Equality<ClassName>().GetHashCode(this);
        }
    }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.