c# बराबर विधि ओवरराइड होने पर GetHashCode को ओवरराइड करना क्यों महत्वपूर्ण है?




override (10)

हमारे साथ सामना करने के लिए दो समस्याएं हैं।

  1. यदि आप GetHashCode() किसी भी फ़ील्ड को बदला जा सकता है तो आप एक समझदार GetHashCode() प्रदान नहीं कर सकते हैं। इसके अलावा एक ऑब्जेक्ट को कभी भी संग्रह में उपयोग नहीं किया जाएगा जो GetHashCode() पर निर्भर करता है। तो GetHashCode() को लागू करने की लागत अक्सर इसके लायक नहीं है, या यह संभव नहीं है।

  2. अगर कोई आपके ऑब्जेक्ट को उस संग्रह में रखता है जो GetHashCode() कॉल करता है और आपने GetHashCode() सही तरीके से व्यवहार किए बिना Equals() को ओवरराइड किया है, तो वह व्यक्ति समस्या को ट्रैक करने में दिन व्यतीत कर सकता है।

इसलिए डिफ़ॉल्ट रूप से मैं करता हूं।

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Some comment to explain if there is a real problem with providing GetHashCode() 
        // or if I just don't see a need for it for the given class
        throw new Exception("Sorry I don't know what GetHashCode should do for this class");
    }
}

निम्नलिखित वर्ग को देखते हुए

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Which is preferred?

        return base.GetHashCode();

        //return this.FooId.GetHashCode();
    }
}

मैंने Equals विधि को ओवरराइड कर दिया है क्योंकि Foo Foo टेबल के लिए एक पंक्ति का प्रतिनिधित्व करता है। GetHashCode को ओवरराइड करने के लिए पसंदीदा तरीका कौन सा है?

GetHashCode को ओवरराइड करना क्यों महत्वपूर्ण है?


बराबर ओवरराइड करके आप मूल रूप से बता रहे हैं कि आप वह हैं जो बेहतर तरीके से जानते हैं कि किसी दिए गए प्रकार के दो उदाहरणों की तुलना कैसे करें, ताकि आप सर्वोत्तम हैश कोड प्रदान करने के लिए सर्वश्रेष्ठ उम्मीदवार बन सकें।

यह एक उदाहरण है कि ReSharper आपके लिए GetHashCode () फ़ंक्शन कैसे लिखता है:

public override int GetHashCode()
{
    unchecked
    {
        var result = 0;
        result = (result * 397) ^ m_someVar1;
        result = (result * 397) ^ m_someVar2;
        result = (result * 397) ^ m_someVar3;
        result = (result * 397) ^ m_someVar4;
        return result;
    }
}

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


Equals() ओवरराइड करते समय कृपया null खिलाफ obj पैरामीटर को देखना न भूलें। और प्रकार की तुलना भी करें।

public override bool Equals(object obj)
{
    if (obj == null || GetType() != obj.GetType())
        return false;

    Foo fooItem = obj as Foo;

    return fooItem.FooId == this.FooId;
}

इसका कारण यह है: Equals तुलना में Equals झूठी वापसी करनी चाहिए। http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx भी देखें


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


ऐसा इसलिए है क्योंकि ढांचे के लिए आवश्यक है कि दो वस्तुओं को एक ही हैशकोड होना चाहिए। यदि आप दो ऑब्जेक्ट्स की विशेष तुलना करने के लिए बराबर विधि को ओवरराइड करते हैं और दो ऑब्जेक्ट्स विधि द्वारा समान मानते हैं, तो दो ऑब्जेक्ट्स का हैश कोड भी वही होना चाहिए। (शब्दकोश और हैशटेबल्स इस सिद्धांत पर भरोसा करते हैं)।


बस उपरोक्त उत्तरों को जोड़ने के लिए:

यदि आप बराबर ओवरराइड नहीं करते हैं तो डिफ़ॉल्ट व्यवहार यह है कि वस्तुओं के संदर्भों की तुलना की जाती है। यह हैशकोड पर लागू होता है - डिफ़ॉल्ट आरोपण आमतौर पर संदर्भ के स्मृति पते पर आधारित होता है। चूंकि आपने बराबर ओवरराइड किया है, इसका मतलब है कि सही व्यवहार समान है, जो आपने बराबर पर लागू किया है, संदर्भों की तुलना करना है, इसलिए आपको हैशकोड के लिए भी ऐसा करना चाहिए।

आपकी कक्षा के ग्राहक बराबर विधि के लिए हैशकोड के समान तर्क प्राप्त करेंगे, उदाहरण के लिए, linqu विधियों जो IEqualityComparer का उपयोग करते हैं, पहले हैशकोड की तुलना करें और केवल तभी वे बराबर हैं, वे बराबर () विधि की तुलना करेंगे जो अधिक महंगा हो सकता है चलाने के लिए, अगर हमने हैशकोड को लागू नहीं किया है, तो बराबर ऑब्जेक्ट में शायद अलग हैशकोड होंगे (क्योंकि उनके पास अलग-अलग मेमोरी पता है) और गलत तरीके से निर्धारित किया जाएगा जैसा बराबर नहीं है (बराबर () भी हिट नहीं होगा)।

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


हां, यह महत्वपूर्ण है कि आपका आइटम किसी शब्दकोश में कुंजी के रूप में उपयोग किया जाएगा, या HashSet<T> , आदि - क्योंकि इसका उपयोग बाल्टी में आइटम समूह करने के लिए किया जाता है (कस्टम IEqualityComparer<T> ) की अनुपस्थिति में। यदि दो आइटमों के लिए हैश-कोड मेल नहीं खाता है, तो उन्हें कभी भी बराबर नहीं माना जा सकता है ( Equals कभी नहीं कहा जाएगा)।

GetHashCode() विधि Equals तर्क को प्रतिबिंबित करना चाहिए; नियम हैं:

  • यदि दो चीजें बराबर हैं ( Equals(...) == true ) तो उन्हें GetHashCode() लिए एक ही मान वापस करना होगा
  • अगर GetHashCode() बराबर है, तो उनके लिए समान नहीं होना आवश्यक है; यह एक टक्कर है, और Equals को यह देखने के लिए बुलाया जाएगा कि यह वास्तविक समानता है या नहीं।

इस मामले में, ऐसा लगता है कि " return FooId; " एक उपयुक्त GetHashCode() कार्यान्वयन है। यदि आप एकाधिक गुणों का परीक्षण कर रहे हैं, तो विकर्ण टकराव को कम करने के लिए नीचे दिए गए कोड का उपयोग करके उन्हें जोड़ना आम है (यानी कि new Foo(3,5) पास new Foo(5,3) के लिए एक अलग हैश कोड है):

int hash = 13;
hash = (hash * 7) + field1.GetHashCode();
hash = (hash * 7) + field2.GetHashCode();
...
return hash;

ओह - सुविधा के लिए, आप Equals और GetHashCode ओवरराइड करते समय == और != ऑपरेटर प्रदान करने पर भी विचार कर सकते हैं।

जब आप यह गलत पाते हैं तो क्या होता है इसका एक प्रदर्शन here


कैसा रहेगा:

public override int GetHashCode()
{
    return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
}

मान लीजिए प्रदर्शन एक मुद्दा नहीं है :)


यह मेरी समझ है कि मूल GetHashCode () ऑब्जेक्ट का मेमोरी पता देता है, इसलिए यदि आप दो अलग-अलग ऑब्जेक्ट्स की तुलना करना चाहते हैं तो इसे ओवरराइड करना आवश्यक है।

संपादित: यह गलत था, मूल GetHashCode () विधि 2 मानों की समानता को आश्वस्त नहीं कर सकती है। यद्यपि बराबर वस्तुएं समान हैश कोड लौटाती हैं।


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

   public override int GetHashCode()
   {
      return base.GetHashCode();
   }

(बेशक मैं चेतावनी को बंद करने के लिए #pragma का भी उपयोग कर सकता हूं लेकिन मैं इस तरह से पसंद करता हूं।)

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


   class A
   {
      public int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //WRONG! Value is not constant during the instance's life time
      }
   }    

दूसरी तरफ, यदि मूल्य बदला नहीं जा सकता है तो इसका उपयोग करना ठीक है:


   class A
   {
      public readonly int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //OK  Value is read-only and can't be changed during the instance's life time
      }
   }





hashcode