.net हरण एक ओवरराइड सिस्टम के लिए सबसे अच्छा एल्गोरिदम क्या है। ऑब्जेक्ट। गेटहाशकोड?




मानक एल्गोरिथ्म (13)

Pretty much similar to nightcoder's solution except it's easier to raise primes if you want to.

PS: This is one of those times where you puke a little in your mouth, knowing that this could be refactored into one method with 9 default's but it would be slower, so you just close your eyes and try to forget about it.

/// <summary>
/// Try not to look at the source code. It works. Just rely on it.
/// </summary>
public static class HashHelper
{
    private const int PrimeOne = 17;
    private const int PrimeTwo = 23;

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();
            hash = hash * PrimeTwo + arg9.GetHashCode();
            hash = hash * PrimeTwo + arg10.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8, T9>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();
            hash = hash * PrimeTwo + arg9.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7, T8>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();
            hash = hash * PrimeTwo + arg8.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6, T7>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();
            hash = hash * PrimeTwo + arg7.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5, T6>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();
            hash = hash * PrimeTwo + arg6.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4, T5>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();
            hash = hash * PrimeTwo + arg5.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();
            hash = hash * PrimeTwo + arg4.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();
            hash = hash * PrimeTwo + arg3.GetHashCode();

            return hash;
        }
    }

    public static int GetHashCode<T1, T2>(T1 arg1, T2 arg2)
    {
        unchecked
        {
            int hash = PrimeOne;
            hash = hash * PrimeTwo + arg1.GetHashCode();
            hash = hash * PrimeTwo + arg2.GetHashCode();

            return hash;
        }
    }
}

.NET System.Object.GetHashCode विधि में .NET बेस क्लास लाइब्रेरीज़ में, कई स्थानों पर उपयोग किया जाता है। खासकर जब संग्रह में वस्तुओं को तेजी से या समानता निर्धारित करने के लिए। क्या मेरे कस्टम वर्गों के लिए GetHashCode ओवरराइड को कार्यान्वित करने के तरीके पर एक मानक एल्गोरिदम / सर्वोत्तम अभ्यास है, इसलिए मैं प्रदर्शन को कम नहीं करता हूं?


Microsoft lead for several way of hashing...

//for classes that contain a single int value
return this.value;

//for classes that contain multiple int value
return x ^ y;

//for classes that contain single number bigger than int    
return ((int)value ^ (int)(value >> 32)); 

//for classes that contain class instance fields which inherit from object
return obj1.GetHashCode();

//for classes that contain multiple class instance fields which inherit from object
return obj1.GetHashCode() ^ obj2.GetHashCode() ^ obj3.GetHashCode(); 

I can guess that for multiple big int you can use this:

int a=((int)value1 ^ (int)(value1 >> 32));
int b=((int)value2 ^ (int)(value2 >> 32));
int c=((int)value3 ^ (int)(value3 >> 32));
return a ^ b ^ c;

And same for multi-type: all converted first to int using GetHashCode() then the int values will be xor'ed and the result is your hash.

For those who use hash as ID (I mean an unique value), hash is naturally limited to a number of digits, I think it was 5 bytes for hashing algorithm, at least MD5.

You may turn multiple values to a hashed value and some of them be same, so don't use it as an identifier. (maybe some day I am going to use your component)


मैं आमतौर पर जोश ब्लोच के शानदार प्रभावशाली जावा में दिए गए कार्यान्वयन की तरह कुछ जाता हूं। यह तेज़ है और एक बहुत अच्छा हैश बनाता है जो टकराव का कारण बनने की संभावना नहीं है। दो अलग-अलग प्राइम नंबर चुनें, उदाहरण के लिए 17 और 23, और करें:

public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = 17;
        // Suitable nullity checks etc, of course :)
        hash = hash * 23 + field1.GetHashCode();
        hash = hash * 23 + field2.GetHashCode();
        hash = hash * 23 + field3.GetHashCode();
        return hash;
    }
}

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

यह दो मुख्य कारणों से XOR आईएनएचकोड्स के सामान्य अभ्यास से बेहतर है। मान लें कि हमारे पास दो int फ़ील्ड के साथ एक प्रकार है:

XorHash(x, x) == XorHash(y, y) == 0 for all x, y
XorHash(x, y) == XorHash(y, x) for all x, y

वैसे, पहले एल्गोरिदम वर्तमान में अज्ञात प्रकारों के लिए सी # कंपाइलर द्वारा उपयोग किया जाता है।

यह पृष्ठ काफी कुछ विकल्प देता है। मुझे लगता है कि ज्यादातर मामलों के लिए उपरोक्त "पर्याप्त अच्छा" है और यह याद रखना और सही होने के लिए अविश्वसनीय रूप से आसान है। FNV विकल्प समान सरल है, लेकिन संयोजन संयोजन के रूप में ADD बजाय विभिन्न स्थिरांक और XOR का उपयोग करता है। यह नीचे दिए गए कोड की तरह दिखता है , लेकिन सामान्य एफएनवी एल्गोरिदम व्यक्तिगत बाइट्स पर काम करता है, इसलिए 32-बिट हैश मान के बजाए प्रति बाइट प्रति पुनरावृत्ति करने के लिए इसे संशोधित करने की आवश्यकता होगी। एफएनवी को डेटा की परिवर्तनीय लंबाई के लिए भी डिज़ाइन किया गया है, जबकि जिस तरह से हम इसका उपयोग कर रहे हैं वह हमेशा फ़ील्ड मानों की समान संख्या के लिए है। इस उत्तर पर टिप्पणियों से पता चलता है कि यहां कोड वास्तव में ऊपर के अतिरिक्त दृष्टिकोण के रूप में भी काम नहीं करता है (नमूना केस परीक्षण में)।

// Note: Not quite FNV!
public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = (int) 2166136261;
        // Suitable nullity checks etc, of course :)
        hash = (hash * 16777619) ^ field1.GetHashCode();
        hash = (hash * 16777619) ^ field2.GetHashCode();
        hash = (hash * 16777619) ^ field3.GetHashCode();
        return hash;
    }
}

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

documentation अनुसार:

आप अपरिवर्तनीय संदर्भ प्रकारों के लिए GetHashCode को ओवरराइड कर सकते हैं। आम तौर पर, परिवर्तनीय संदर्भ प्रकारों के लिए, आपको केवल GetHashCode को ओवरराइड करना चाहिए यदि:

  • आप उन क्षेत्रों से हैश कोड की गणना कर सकते हैं जो उत्परिवर्तनीय नहीं हैं; या
  • आप यह सुनिश्चित कर सकते हैं कि एक म्यूटेबल ऑब्जेक्ट का हैश कोड तब नहीं बदलेगा जब ऑब्जेक्ट उस संग्रह में निहित है जो उसके हैश कोड पर निर्भर करता है।

हाल ही में जब तक मेरा जवाब जॉन स्कीट के बहुत करीब था। हालांकि, मैंने हाल ही में एक परियोजना शुरू की है जो दो हैश टेबल का उपयोग करती है, जो हैश टेबल है जहां आंतरिक तालिका का आकार 8, 16, 32 इत्यादि है। वहां प्रमुख संख्या के आकार का पक्ष लेने का एक अच्छा कारण है, लेकिन वहां बिजली के दो आकारों के कुछ फायदे भी हैं।

और यह बहुत ज्यादा चूसा। तो कुछ प्रयोग और शोध के बाद मैंने निम्नलिखित के साथ अपने हैंश को दोबारा शुरू कर दिया:

public static int ReHash(int source)
{
  unchecked
  {
    ulong c = 0xDEADBEEFDEADBEEF + (ulong)source;
    ulong d = 0xE2ADBEEFDEADBEEF ^ c;
    ulong a = d += c = c << 15 | c >> -15;
    ulong b = a += d = d << 52 | d >> -52;
    c ^= b += a = a << 26 | a >> -26;
    d ^= c += b = b << 51 | b >> -51;
    a ^= d += c = c << 28 | c >> -28;
    b ^= a += d = d << 9 | d >> -9;
    c ^= b += a = a << 47 | a >> -47;
    d ^= c += b << 54 | b >> -54;
    a ^= d += c << 32 | c >> 32;
    a += d << 25 | d >> -25;
    return (int)(a >> 1);
  }
}

और फिर मेरी शक्ति की दो हैश टेबल अब और चूसना नहीं था।

यह मुझे परेशान हालांकि, क्योंकि उपर्युक्त काम नहीं करना चाहिए। या अधिक सटीक, यह तब तक काम नहीं करना चाहिए जब तक कि मूल GetHashCode() एक विशेष तरीके से खराब न हो।

हैशकोड को फिर से मिलाकर एक महान हैशकोड में सुधार नहीं हो सकता है, क्योंकि केवल एक ही संभावित प्रभाव यह है कि हम कुछ और टकराव पेश करते हैं।

एक हैश कोड को दोबारा मिलाकर एक भयानक हैश कोड में सुधार नहीं हो सकता है, क्योंकि एकमात्र संभावित प्रभाव हम बदलते हैं जैसे मूल्य 53 पर बड़ी संख्या में टकराव की संख्या बड़ी संख्या में 18,3487,291 है।

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

संपादित करें: मैं ओपन-एड्रेसिंग का भी उपयोग कर रहा था, जिसने टकराव की संवेदनशीलता में भी वृद्धि की होगी, शायद यह तथ्य है कि यह शक्ति-दो-दो थी।

और अच्छी तरह से, यह परेशान कर रहा था कि string.GetHashCode() कितनी है। .NET (या here अध्ययन string.GetHashCode() में string.GetHashCode() कार्यान्वयन इस तरह से सुधार किया जा सकता है (कम टकराव के कारण लगभग 20-30 गुना तेजी से चलने वाले परीक्षणों के क्रम में) और अधिक परेशान मेरे अपने हैश कोड में सुधार किया जा सकता है (उससे भी ज्यादा)।

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

तो मैंने उस परियोजना को एक तरफ रखा (यह वैसे भी एक पालतू परियोजना थी) और यह शुरू करना शुरू कर दिया कि कैसे .NET में एक अच्छा, अच्छी तरह से वितरित हैश कोड उत्पन्न करना है।

अंत में मैं SpookyHash को .NET पर पोर्ट करने पर बस गया। दरअसल उपरोक्त कोड 32-बिट इनपुट से 32-बिट आउटपुट उत्पन्न करने के लिए स्पूकीशैश का उपयोग करने का एक तेज़-पथ संस्करण है।

अब, स्पूकी हैश कोड के टुकड़े को याद रखने के लिए एक अच्छा जल्दी नहीं है। इसका मेरा बंदरगाह इतना कम है क्योंकि मैंने इसे बेहतर गति के लिए बहुत कुछ सौंप दिया है। लेकिन यही कोड पुन: उपयोग है।

तब मैंने उस परियोजना को एक तरफ रखा, क्योंकि मूल परियोजना ने बेहतर हैश कोड बनाने के सवाल का सवाल उठाया था, ताकि परियोजना ने एक बेहतर .NET memcpy का उत्पादन करने के सवाल का उत्पादन किया।

फिर मैं वापस आया, और एक हैश कोड में सभी देशी प्रकारों ( decimal except को छोड़कर) के बारे में आसानी से फ़ीड करने के लिए बहुत सारे अधिभार उत्पन्न किए।

यह तेज़ है, जिसके लिए बॉब जेनकिंस अधिकांश क्रेडिट का हकदार है क्योंकि उसका मूल कोड मैंने अभी भी बंद किया है, विशेष रूप से 64-बिट मशीनों पर जो एल्गोरिदम को ‡ के लिए अनुकूलित किया गया है।

पूरा कोड https://bitbucket.org/JonHanna/spookilysharp/src पर देखा जा सकता है लेकिन मान लें कि उपर्युक्त कोड इसका एक सरल संस्करण है।

हालांकि, चूंकि यह अब पहले ही लिखा गया है, इसलिए इसका उपयोग अधिक आसानी से कर सकता है:

public override int GetHashCode()
{
  var hash = new SpookyHash();
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

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

private static long hashSeed0 = Environment.TickCount;
private static long hashSeed1 = DateTime.Now.Ticks;
public override int GetHashCode()
{
  //produce different hashes ever time this application is restarted
  //but remain consistent in each run, so attackers have a harder time
  //DoSing the hash tables.
  var hash = new SpookyHash(hashSeed0, hashSeed1);
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

* इसमें एक बड़ा आश्चर्य यह है कि एक रोटेशन विधि को हाथ-इनलाइन करना (x << n) | (x >> -n) (x << n) | (x >> -n) बेहतर चीजें। मुझे यकीन था कि जिटर ने मेरे लिए यह रेखांकित किया होगा, लेकिन प्रोफाइलिंग अन्यथा दिखाया गया है।

decimal .NET परिप्रेक्ष्य से मूल नहीं है हालांकि यह सी # से है। इसके साथ समस्या यह है कि इसका अपना GetHashCode() सटीक रूप से महत्वपूर्ण मानता है जबकि इसके Equals() नहीं होता है। दोनों वैध विकल्प हैं, लेकिन इस तरह मिश्रित नहीं हैं। अपने स्वयं के संस्करण को लागू करने में, आपको एक या दूसरे को चुनने की आवश्यकता है, लेकिन मुझे नहीं पता कि आप कौन सी चाहेंगे।

तुलना के माध्यम से ‡। यदि एक स्ट्रिंग पर प्रयोग किया जाता है, तो 64 बिट्स पर स्पूकीशैश स्ट्रिंग से काफी तेज है। 32 बिट्स पर string.GetHashCode() स्ट्रिंग से थोड़ा तेज़ है। 64 बिट्स पर गेटहाशकोड string.GetHashCode() , जो 32 बिट्स पर स्पूकीशैश से काफी तेज़ है, हालांकि अभी भी तेज़ है उचित विकल्प होने के लिए पर्याप्त है।


ReSharper users can generate GetHashCode, Equals, and others with ReSharper -> Edit -> Generate Code -> Equality Members .

// ReSharper's GetHashCode looks like this
public override int GetHashCode() {
    unchecked {
        int hashCode = Id;
        hashCode = (hashCode * 397) ^ IntMember;
        hashCode = (hashCode * 397) ^ OtherIntMember;
        hashCode = (hashCode * 397) ^ (RefMember != null ? RefMember.GetHashCode() : 0);
        // ...
        return hashCode;
    }
}

Most of my work is done with database connectivity which means that my classes all have a unique identifier from the database. I always use the ID from the database to generate the hashcode.

// Unique ID from database
private int _id;

...    
{
  return _id.GetHashCode();
}

मेरे पास हेल्पर लाइब्रेरी में हैशिंग क्लास है जिसका उपयोग मैं इस उद्देश्य के लिए करता हूं।

/// <summary> 
/// This is a simple hashing function from Robert Sedgwicks Hashing in C book.
/// Also, some simple optimizations to the algorithm in order to speed up
/// its hashing process have been added. from: www.partow.net
/// </summary>
/// <param name="input">array of objects, parameters combination that you need
/// to get a unique hash code for them</param>
/// <returns>Hash code</returns>
public static int RSHash(params object[] input)
{
    const int b = 378551;
    int a = 63689;
    int hash = 0;

    // If it overflows then just wrap around
    unchecked
    {
        for (int i = 0; i < input.Length; i++)
        {
            if (input[i] != null)
            {
                hash = hash * a + input[i].GetHashCode();
                a = a * b;
            }
        }
    }

    return hash;
}

फिर, बस आप इसका उपयोग इस प्रकार कर सकते हैं:

public override int GetHashCode()
{
    return Hashing.RSHash(_field1, _field2, _field3);
}

मैंने अपने प्रदर्शन का आकलन नहीं किया, इसलिए किसी भी प्रतिक्रिया का स्वागत है।


Here is another fluent implementation of the algorithm posted above by Jon Skeet , but which includes no allocations or boxing operations:

public static class Hash
{
    public const int Base = 17;

    public static int HashObject(this int hash, object obj)
    {
        unchecked { return hash * 23 + (obj == null ? 0 : obj.GetHashCode()); }
    }

    public static int HashValue<T>(this int hash, T value)
        where T : struct
    {
        unchecked { return hash * 23 + value.GetHashCode(); }
    }
}

उपयोग:

public class MyType<T>
{
    public string Name { get; set; }

    public string Description { get; set; }

    public int Value { get; set; }

    public IEnumerable<T> Children { get; set; }

    public override int GetHashCode()
    {
        return Hash.Base
            .HashObject(this.Name)
            .HashObject(this.Description)
            .HashValue(this.Value)
            .HashObject(this.Children);
    }
}

The compiler will ensure HashValue is not called with a class due to the generic type constraint. But there is no compiler support for HashObject since adding a generic argument also adds a boxing operation.


यहां मेरा सरल दृष्टिकोण है। मैं इसके लिए क्लासिक बिल्डर पैटर्न का उपयोग कर रहा हूं। यह टाइपएफ़ है (कोई मुक्केबाजी / अनबॉक्सिंग नहीं) और .NET 2.0 (कोई एक्सटेंशन विधियां इत्यादि) के साथ भी compatbile।

इसका उपयोग इस तरह किया जाता है:

public override int GetHashCode()
{
    HashBuilder b = new HashBuilder();
    b.AddItems(this.member1, this.member2, this.member3);
    return b.Result;
} 

और यहाँ Acutal बिल्डर वर्ग है:

internal class HashBuilder
{
    private const int Prime1 = 17;
    private const int Prime2 = 23;
    private int result = Prime1;

    public HashBuilder()
    {
    }

    public HashBuilder(int startHash)
    {
        this.result = startHash;
    }

    public int Result
    {
        get
        {
            return this.result;
        }
    }

    public void AddItem<T>(T item)
    {
        unchecked
        {
            this.result = this.result * Prime2 + item.GetHashCode();
        }
    }

    public void AddItems<T1, T2>(T1 item1, T2 item2)
    {
        this.AddItem(item1);
        this.AddItem(item2);
    }

    public void AddItems<T1, T2, T3>(T1 item1, T2 item2, T3 item3)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
    }

    public void AddItems<T1, T2, T3, T4>(T1 item1, T2 item2, T3 item3, 
        T4 item4)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
    }

    public void AddItems<T1, T2, T3, T4, T5>(T1 item1, T2 item2, T3 item3, 
        T4 item4, T5 item5)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
        this.AddItem(item5);
    }        

    public void AddItems<T>(params T[] items)
    {
        foreach (T item in items)
        {
            this.AddItem(item);
        }
    }
}

माइक्रोसॉफ्ट पहले से ही एक अच्छा जेनेरिक हैशकोड जनरेटर प्रदान करता है: बस अपनी संपत्ति / फ़ील्ड मानों को एक अनाम प्रकार में कॉपी करें और हैश इसे:

new { PropA, PropB, PropC, PropD }.GetHashCode();

यह किसी भी संपत्ति के लिए काम करेगा। यह मुक्केबाजी या अतिरिक्त संसाधनों का उपयोग नहीं करता है। यह सिर्फ पहले से ही अज्ञात प्रकार के ढांचे में लागू एल्गोरिदम का उपयोग करता है।


As of https://github.com/dotnet/coreclr/pull/14863 , there is a new way to generate hash codes that is super simple! Just write

public override int GetHashCode()
    => HashCode.Combine(field1, field2, field3);

This will generate a quality hash code without you having to worry about the implementation details.


ज्यादातर मामलों में जहां इक्वाल्स () एकाधिक फ़ील्ड की तुलना करता है, इससे कोई फर्क नहीं पड़ता कि आपका GetHash () एक फ़ील्ड या कई पर हैश हो। आपको बस यह सुनिश्चित करना होगा कि हैश की गणना वास्तव में सस्ता है ( कोई आवंटन , कृपया) और तेज़ ( कोई भारी गणना नहीं और निश्चित रूप से कोई डेटाबेस कनेक्शन नहीं) और एक अच्छा वितरण प्रदान करता है।

भारी भारोत्तोलन बराबर () विधि का हिस्सा होना चाहिए; जितना संभव हो उतना सामान पर कॉलिंग इक्वाल्स () को सक्षम करने के लिए हैश को बहुत सस्ता ऑपरेशन होना चाहिए।

और एक अंतिम युक्ति: GetHashCode () पर एकाधिक अनुप्रयोग रनों पर स्थिर होने पर भरोसा न करें । कई .Net प्रकार अपने हैश कोड को पुनरारंभ करने के बाद समान रहने की गारंटी नहीं देते हैं, इसलिए आपको केवल स्मृति डेटा संरचनाओं में GetHashCode () के मान का उपयोग करना चाहिए।


यहां मेरी सहायक कक्षा है जो जॉन स्कीट के कार्यान्वयन का उपयोग करती है ।

public static class HashCode
{
    public const int Start = 17;

    public static int Hash<T>(this int hash, T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked((hash * 31) + h);
    }
}

उपयोग:

public override int GetHashCode()
{
    return HashCode.Start
        .Hash(field1)
        .Hash(field2)
        .Hash(field3);
}

यदि आप System.Int32 के लिए एक एक्सटेंशन विधि लिखने से बचना चाहते हैं:

public struct HashCode
{
    private readonly int hashCode;

    public HashCode(int hashCode)
    {
        this.hashCode = hashCode;
    }

    public static HashCode Start { get; } = new HashCode(17);

    public static implicit operator int(HashCode hashCode)
        => hashCode.GetHashCode();

    public HashCode Hash<T>(T obj)
    {
        var h = EqualityComparer<T>.Default.GetHashCode(obj);
        return unchecked(new HashCode((this.hashCode * 31) + h));
    }

    public override int GetHashCode()
        => this.hashCode;
}

यह अभी भी सामान्य है, यह अभी भी किसी भी ढेर आवंटन से बचाता है और इसका उपयोग बिल्कुल उसी तरह किया जाता है:

public override int GetHashCode()
{
    // This time `HashCode.Start` is not an `Int32`, it's a `HashCode` instance.
    // And the result is implicitly converted to `Int32`.
    return HashCode.Start
        .Hash(field1)
        .Hash(field2)     
        .Hash(field3);
}

मार्टिन की टिप्पणी के बाद अपडेट करें:

obj != null कारण मुक्केबाजी तो मैं डिफ़ॉल्ट तुलनात्मक के लिए स्विच किया।

  • डिफ़ॉल्ट तुलनात्मक प्रदर्शन के संबंध में यह उत्तर देखें।
  • शून्य प्रश्नों के हैश कोड के बारे में चर्चा के लिए यह प्रश्न देखें।




gethashcode