Object.GetHashCode () के लिए डिफ़ॉल्ट कार्यान्वयन


162

GetHashCode()कार्य के लिए डिफ़ॉल्ट कार्यान्वयन कैसे होता है ? और क्या यह संरचनाओं, वर्गों, सरणियों आदि को कुशलतापूर्वक और पर्याप्त रूप से संभालता है?

मैं यह तय करने की कोशिश कर रहा हूं कि मुझे किन मामलों में अपना पैक लगाना चाहिए और किन मामलों में मैं अच्छी तरह से करने के लिए डिफ़ॉल्ट कार्यान्वयन पर सुरक्षित रूप से भरोसा कर सकता हूं। यदि संभव हो, तो मैं पहिया को सुदृढ़ नहीं करना चाहता।


टिप्पणी पर एक नज़र डालें जो मैंने लेख पर छोड़ दिया है: stackoverflow.com/questions/763731/gethashcode-extension-method
पॉल वेस्टकॉट


34
एक तरफ: यदि आप कर सकते हैं प्राप्त डिफ़ॉल्ट hashCode (तब भी जब GetHashCode()का उपयोग करके ओवरराइड की गई है)System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(obj)
मार्क Gravell

@MarcGravell ने इस योगदान के लिए धन्यवाद, मैं वास्तव में इस उत्तर के लिए खोज रहा था।
एंड्रयू सविनाख

@MarcGravell लेकिन मैं इसे अन्य विधि के साथ कैसे करूंगा?
टॉम ज़ातो -

जवाबों:


86
namespace System {
    public class Object {
        [MethodImpl(MethodImplOptions.InternalCall)]
        internal static extern int InternalGetHashCode(object obj);

        public virtual int GetHashCode() {
            return InternalGetHashCode(this);
        }
    }
}

InternalGetHashCode को ObjectNative :: CLH में GetHashCode फ़ंक्शन के लिए मैप किया जाता है , जो इस प्रकार है:

FCIMPL1(INT32, ObjectNative::GetHashCode, Object* obj) {  
    CONTRACTL  
    {  
        THROWS;  
        DISABLED(GC_NOTRIGGER);  
        INJECT_FAULT(FCThrow(kOutOfMemoryException););  
        MODE_COOPERATIVE;  
        SO_TOLERANT;  
    }  
    CONTRACTL_END;  

    VALIDATEOBJECTREF(obj);  

    DWORD idx = 0;  

    if (obj == 0)  
        return 0;  

    OBJECTREF objRef(obj);  

    HELPER_METHOD_FRAME_BEGIN_RET_1(objRef);        // Set up a frame  

    idx = GetHashCodeEx(OBJECTREFToObject(objRef));  

    HELPER_METHOD_FRAME_END();  

    return idx;  
}  
FCIMPLEND

GetHashCodeEx का पूर्ण कार्यान्वयन काफी बड़ा है, इसलिए इसे C ++ स्रोत कोड से लिंक करना आसान है ।


5
वह प्रलेखन उद्धरण बहुत प्रारंभिक संस्करण से आया होगा। यह वर्तमान MSDN लेखों में इस तरह नहीं लिखा गया है, शायद इसलिए कि यह काफी गलत है।
हंस पसंत

4
उन्होंने शब्दांकन को बदल दिया, हां, लेकिन यह अभी भी मूल रूप से एक ही बात कहता है: "नतीजतन, इस पद्धति के डिफ़ॉल्ट कार्यान्वयन को हैशिंग उद्देश्यों के लिए एक अद्वितीय वस्तु पहचानकर्ता के रूप में उपयोग नहीं किया जाना चाहिए।"
डेविड ब्राउन

7
प्रलेखन क्यों दावा करता है कि कार्यान्वयन हैशिंग के लिए विशेष रूप से उपयोगी नहीं है? यदि कोई वस्तु स्वयं के बराबर है और कुछ भी नहीं है, तो कोई भी हैश कोड पद्धति जो किसी दिए गए ऑब्जेक्ट उदाहरण के लिए समान मान लौटाएगा, और आम तौर पर विभिन्न उदाहरणों के लिए अलग-अलग मान लौटाएगा, समस्या क्या है?
सुपरकैट

3
@ ta.speot.is: यदि आप चाहते हैं कि यह निर्धारित किया जाए कि क्या किसी विशेष उदाहरण को पहले ही एक शब्दकोश में जोड़ा गया है, तो संदर्भ समानता एकदम सही है। स्ट्रिंग के साथ, जैसा कि आप ध्यान दें, आमतौर पर एक और अधिक दिलचस्पी है कि क्या पात्रों के समान अनुक्रम वाले स्ट्रिंग को पहले ही जोड़ा जा चुका है। इसलिए stringओवरराइड करता है GetHashCode। दूसरी ओर, मान लीजिए कि आप इस बात की गिनती रखना चाहते हैं कि कितनी बार विभिन्न Paintघटनाओं को नियंत्रित करता है । आप एक का उपयोग कर सकते हैं Dictionary<Object, int[]>(प्रत्येक int[]संग्रहीत बिल्कुल एक आइटम धारण करेगा)।
सुपरकैट

6
@ It'sNotALie। फिर कॉपी करने के लिए Archive.org को धन्यवाद ;-)
RobIII

88

एक वर्ग के लिए, चूक अनिवार्य रूप से संदर्भ समानता है, और यह आमतौर पर ठीक है। यदि कोई संरचना लिख ​​रहा है, तो समानता को ओवरराइड करना अधिक आम है (कम से कम मुक्केबाजी से बचने के लिए), लेकिन यह बहुत दुर्लभ है आप वैसे भी एक संरचना लिखते हैं!

समानता को ओवरराइड करते समय, आपके पास हमेशा एक मिलान होना चाहिए Equals()और GetHashCode()(दो मानों के लिए, यदि Equals()रिटर्न सही है तो उन्हें उसी हैश-कोड को वापस करना होगा , लेकिन कन्वर्सेशन की आवश्यकता नहीं है) - और यह भी ==/ !=ऑपरेटरों को प्रदान करना आम है , और अक्सर लागू IEquatable<T>भी करें।

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

unchecked // disable overflow, for the unlikely possibility that you
{         // are compiling with overflow-checking enabled
    int hash = 27;
    hash = (13 * hash) + field1.GetHashCode();
    hash = (13 * hash) + field2.GetHashCode();
    return hash;
}

इसका यह फायदा है कि:

  • {1,2} का हैश {2,1} के हैश के समान नहीं है
  • {1,1} का हैश {2,2} के हैश के समान नहीं है

आदि - जो सामान्य हो सकता है अगर सिर्फ एक अनवीटेड योग, या xor ( ^), आदि का उपयोग किया जाए।


एक तथ्य-योग एल्गोरिथ्म के लाभ के बारे में उत्कृष्ट बिंदु; कुछ ऐसा जिसे मैंने पहले महसूस नहीं किया था!
लोफोल

तथ्यात्मक योग नहीं होगा (जैसा कि ऊपर लिखा गया है) कभी-कभी ओवरफ़्लो अपवादों का कारण बनता है?
sinelaw

4
@sinelaw हाँ, यह प्रदर्शन किया जाना चाहिए unchecked। सौभाग्य से, uncheckedसी # में डिफ़ॉल्ट है, लेकिन इसे स्पष्ट करना बेहतर होगा; संपादित
मार्क Gravell

7

ऑब्जेक्ट केGetHashCode लिए विधि का दस्तावेज़ीकरण कहता है "इस विधि के डिफ़ॉल्ट कार्यान्वयन को हैशिंग प्रयोजनों के लिए एक अद्वितीय ऑब्जेक्ट पहचानकर्ता के रूप में उपयोग नहीं किया जाना चाहिए।" और ValueType के लिए एक कहता है "यदि आप व्युत्पन्न प्रकार के गेटहैशकोड विधि को कॉल करते हैं, तो हैश तालिका में कुंजी के रूप में उपयोग के लिए वापसी मूल्य उपयुक्त होने की संभावना नहीं है।"

बुनियादी डेटा प्रकार की तरह byte, short, int, long, charऔर stringएक अच्छा GetHashCode विधि को लागू। Pointउदाहरण के लिए कुछ अन्य कक्षाएं और संरचनाएं, एक ऐसी GetHashCodeविधि को लागू करती हैं जो आपकी विशिष्ट आवश्यकताओं के लिए उपयुक्त हो सकती है या नहीं भी। आपको बस यह देखने की कोशिश करनी है कि क्या यह काफी अच्छा है।

प्रत्येक वर्ग या संरचना के लिए प्रलेखन आपको बता सकता है कि यह डिफ़ॉल्ट कार्यान्वयन को ओवरराइड करता है या नहीं। यदि यह इसे ओवरराइड नहीं करता है तो आपको अपने स्वयं के कार्यान्वयन का उपयोग करना चाहिए। किसी भी वर्ग या संरचना के लिए जो आप स्वयं बनाते हैं जहां आपको GetHashCodeविधि का उपयोग करने की आवश्यकता होती है , आपको अपना स्वयं का कार्यान्वयन करना चाहिए जो हैश कोड की गणना करने के लिए उपयुक्त सदस्यों का उपयोग करता है।


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

@ मारक बजरी: यह निश्चित रूप से नहीं है कि मेरा क्या मतलब है। मैं पिछले पैराग्राफ को समायोजित करूंगा। :)
गुफा

बुनियादी डेटा प्रकार कम से कम मेरे मामले में, एक अच्छा GetHashCode विधि को लागू नहीं करते हैं। उदाहरण के लिए, IntH के लिए GetHashCode खुद नंबर देता है: (123) .GetHashCode () 123 रिटर्न।
fdermishin

5
@ user502144 और इसमें गलत क्या है? यह एक आदर्श अद्वितीय पहचानकर्ता है जिसकी गणना करना आसान है, समानता पर कोई गलत सकारात्मकता के साथ नहीं ...
रिचर्ड रास्ट

@ रिचर्ड रैस्ट: यह ठीक है जब हैशटेबल में इस्तेमाल होने पर चाबियों को छोड़कर बुरी तरह से वितरित किया जा सकता है। इस उत्तर पर एक नज़र डालें: stackoverflow.com/a/1388329/502144
fdermishin

5

चूँकि मुझे ऐसा उत्तर नहीं मिला जो बताता है कि हमें ओवरराइड करना चाहिए GetHashCodeऔर Equalsकस्टम संरचनाओं के लिए और डिफ़ॉल्ट कार्यान्वयन "हैश तालिका में कुंजी के रूप में उपयोग के लिए उपयुक्त होने की संभावना क्यों नहीं है", मैं इस ब्लॉग का लिंक छोड़ दूंगा पोस्ट , जो बताता है कि क्यों एक समस्या का वास्तविक मामला उदाहरण के साथ हुआ।

मैं पूरी पोस्ट पढ़ने की सलाह देता हूं, लेकिन यहां एक सारांश (जोर और स्पष्टीकरण जोड़ा गया है) है।

कारण संरचना के लिए डिफ़ॉल्ट हैश धीमा है और बहुत अच्छा नहीं है:

सीएलआर को जिस तरह से डिज़ाइन किया गया है, एक सदस्य को हर कॉल System.ValueTypeया System.Enumप्रकार में परिभाषित किया जाता है [हो सकता है] एक बॉक्सिंग आवंटन का कारण बन सकता है [...]

हैश फ़ंक्शन के एक कार्यान्वयनकर्ता को एक दुविधा का सामना करना पड़ता है: हैश फ़ंक्शन का अच्छा वितरण करना या इसे तेज़ करना। कुछ मामलों में, उन दोनों को हासिल करना संभव है, लेकिन यह उदारतापूर्वक करना मुश्किल है ValueType.GetHashCode

सभी क्षेत्रों के एक "संयोजन" हैश कोड के कैनोनिकल हैश फ़ंक्शन। लेकिन एक ValueTypeविधि में किसी फ़ील्ड का हैश कोड प्राप्त करने का एकमात्र तरीका प्रतिबिंब का उपयोग करना है । इसलिए, सीएलआर लेखकों ने वितरण से अधिक गति का व्यापार करने का फैसला किया और डिफ़ॉल्ट GetHashCodeसंस्करण बस पहले गैर-शून्य फ़ील्ड का एक हैश कोड लौटाता है और इसे "munges" टाइप आईडी के साथ [...] यह एक उचित व्यवहार है जब तक कि यह नहीं है। । उदाहरण के लिए, यदि आप पर्याप्त रूप से अशुभ हैं और आपकी संरचना के पहले क्षेत्र में अधिकांश उदाहरणों के लिए समान मूल्य है, तो एक हैश फ़ंक्शन हर समय एक ही परिणाम प्रदान करेगा । और, जैसा कि आप कल्पना कर सकते हैं, यह एक कठोर प्रदर्शन प्रभाव का कारण होगा यदि ये उदाहरण एक हैश सेट या एक हैश तालिका में संग्रहीत किए जाते हैं।

[...] परावर्तन आधारित कार्यान्वयन धीमा है । बहुत धीमी गति से।

[...] दोनों ValueType.Equalsऔर ValueType.GetHashCodeएक विशेष अनुकूलन है। यदि एक प्रकार में "पॉइंटर्स" नहीं हैं और ठीक से पैक किया गया है [...] तो अधिक इष्टतम संस्करणों का उपयोग किया जाता है: GetHashCode4 बाइट्स और XORs ब्लॉक के पुनरावृत्तियों Equalsका उपयोग करता है और विधि दो इंस्टेंस का उपयोग करके तुलना करती है memcmp। [...] लेकिन अनुकूलन बहुत मुश्किल है। पहले, यह जानना कठिन है कि अनुकूलन कब सक्षम किया जाता है [...] दूसरा, एक स्मृति तुलना आवश्यक रूप से आपको सही परिणाम नहीं देगी । यहाँ एक सरल उदाहरण है: [...] -0.0और +0.0समान हैं लेकिन अलग-अलग बाइनरी प्रतिनिधित्व हैं।

पोस्ट में वर्णित वास्तविक दुनिया का मुद्दा:

private readonly HashSet<(ErrorLocation, int)> _locationsWithHitCount;
readonly struct ErrorLocation
{
    // Empty almost all the time
    public string OptionalDescription { get; }
    public string Path { get; }
    public int Position { get; }
}

हमने एक टपल का उपयोग किया जिसमें डिफ़ॉल्ट समानता कार्यान्वयन के साथ एक कस्टम संरचना शामिल थी। और दुर्भाग्य से, संरचना में एक वैकल्पिक पहला क्षेत्र था जो लगभग हमेशा [खाली स्ट्रिंग] के बराबर होता है । प्रदर्शन तब तक ठीक था जब तक कि सेट में तत्वों की संख्या एक वास्तविक प्रदर्शन मुद्दा नहीं बन गई, दसियों हज़ारों मदों के साथ एक संग्रह को आरंभ करने में मिनट लग गए।

तो, इस सवाल का जवाब करने के लिए "क्या मामलों में मैं पैक चाहिए मेरे अपने और क्या मामलों मैं सुरक्षित रूप से डिफ़ॉल्ट कार्यान्वयन पर भरोसा कर सकते में", के मामले में कम से कम structs , आप ओवरराइड करना चाहिए Equalsऔर GetHashCodeअपने कस्टम struct एक के रूप में इस्तेमाल किया जा सकता है जब भी एक हैश तालिका में कुंजी या Dictionary
मैं IEquatable<T>मुक्केबाजी से बचने के लिए इस मामले में भी लागू करने की सिफारिश करूंगा ।

जैसा कि अन्य उत्तर में कहा गया है, यदि आप एक वर्ग लिख रहे हैं, तो संदर्भ समानता का उपयोग करने वाला डिफ़ॉल्ट हैश आमतौर पर ठीक है, इसलिए मैं इस मामले में परेशान नहीं करूंगा, जब तक कि आपको ओवरराइड करने की आवश्यकता नहीं है Equals(तब आपको GetHashCodeतदनुसार ओवरराइड करना होगा )।


1

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

फू ए, बी की जांच करते समय बराबर का उपयोग किया जाता है;

अगर (ए == बी)

चूँकि हम जानते हैं कि सूचक मिलान करने की संभावना नहीं है, हम आंतरिक सदस्यों की तुलना कर सकते हैं।

Equals(obj o)
{
    if (o == null) return false;
    MyType Foo = o as MyType;
    if (Foo == null) return false;
    if (Foo.Prop1 != this.Prop1) return false;

    return Foo.Prop2 == this.Prop2;
}

गेटहैशकोड आमतौर पर हैश टेबल द्वारा उपयोग किया जाता है। आपकी कक्षा द्वारा बनाया गया हैशकोड हमेशा राज्य देने वाली कक्षाओं के लिए समान होना चाहिए।

मैं आमतौर पर,

GetHashCode()
{
    int HashCode = this.GetType().ToString().GetHashCode();
    HashCode ^= this.Prop1.GetHashCode();
    etc.

    return HashCode;
}

कुछ कहेंगे कि हैशकोड की गणना केवल एक बार वस्तु के जीवनकाल में की जानी चाहिए, लेकिन मैं इससे सहमत नहीं हूं (और मैं शायद गलत हूं)।

ऑब्जेक्ट द्वारा प्रदान किए गए डिफ़ॉल्ट कार्यान्वयन का उपयोग करना, जब तक कि आपके पास अपनी कक्षाओं में से एक ही संदर्भ न हो, वे एक-दूसरे के बराबर नहीं होंगे। बराबरी और GetHashCode को ओवरराइड करके, आप ऑब्जेक्ट के संदर्भ के बजाय आंतरिक मूल्यों के आधार पर समानता की रिपोर्ट कर सकते हैं।


2
^ = दृष्टिकोण एक हैश पैदा करने के लिए एक विशेष रूप से अच्छा दृष्टिकोण नहीं है - यह आम / उम्मीद के मुताबिक टकराव का एक बहुत पर आधारित होते हैं - उदाहरण के लिए यदि Prop1 = Prop2 = 3.
मार्क Gravell

यदि मान समान हैं, तो मुझे टकराव के साथ कोई समस्या नहीं दिखती क्योंकि ऑब्जेक्ट समान हैं। 13 * हैश + न्यू हैश दिलचस्प लगता है।
बेनेट डिल

2
बेन: इसे Obj1 {Prop1 = 12, Prop2 = 12} और Obj2 {Prop1 = 13, Prop2 = 13} के लिए आज़माएं
Tomáš Kafka

0

यदि आप केवल POCO के साथ काम कर रहे हैं तो आप इस उपयोगिता का उपयोग अपने जीवन को सरल बनाने के लिए कर सकते हैं:

var hash = HashCodeUtil.GetHashCode(
           poco.Field1,
           poco.Field2,
           ...,
           poco.FieldN);

...

public static class HashCodeUtil
{
    public static int GetHashCode(params object[] objects)
    {
        int hash = 13;

        foreach (var obj in objects)
        {
            hash = (hash * 7) + (!ReferenceEquals(null, obj) ? obj.GetHashCode() : 0);
        }

        return hash;
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.