परिचय: वह गलती जो आपके कोड को धीमा कर देती है
\n\nजिसने भी C# में कभी Dictionary या HashSet के साथ काम किया है, उसने Stack Overflow पर यह सवाल ज़रूर देखा होगा। हज़ारों व्यूज़, दर्जनों जवाब, लेकिन बात एक ही है: अगर आपने अपनी क्लास में Equals को Override किया है, लेकिन GetHashCode को भूल गए हैं — तो आपका कोड गलत या बेहद धीमा चल सकता है। क्यों? आइए असली उदाहरणों से समझते हैं।
मान लीजिए आप एक हवाई जहाज़ टिकट बुकिंग सिस्टम बना रहे हैं। आपके पास Flight नाम की एक क्लास है जिसमें यूनिक फ्लाइट नंबर और तारीख है। आप इस क्लास को Dictionary में की के रूप में इस्तेमाल करना चाहते हैं। यह आसान लगता है। लेकिन सही GetHashCode के बिना, आपकी Dictionary एक साधारण लिस्ट में बदल सकती है जिसमें सर्च की स्पीड O(n) हो जाती है, O(1) नहीं।
हैश टेबल का जादू: C# में Dictionary कैसे काम करता है
\n\nसमस्या को समझने के लिए, Dictionary के अंदर झाँकना ज़रूरी है। अंदर से यह "बकेट्स" (buckets) का एक ऐरे है। जब आप कोई एलिमेंट जोड़ते हैं, C# की के लिए GetHashCode() मेथड को कॉल करता है ताकि बकेट का नंबर तय हो सके। अगर उस बकेट में पहले से एलिमेंट हैं, तो उनमें से हर एक से तुलना करने के लिए Equals() कॉल होता है। यही तालमेल बिजली की गति से सर्च करना संभव बनाता है।
वह अनुबंध जिसे तोड़ना नहीं चाहिए
\nC# में एक अनकहा लेकिन सख्त अनुबंध है:
\n- \n
- अगर
Equals()दो ऑब्जेक्ट्स के लिएtrueलौटाता है, तो उनके हैश कोड ज़रूर बराबर होने चाहिए। \n - अगर हैश कोड अलग हैं, तो
Equals()कभी कॉल नहीं होगा (यह एक ऑप्टिमाइज़ेशन है)। \n - उल्टा ज़रूरी नहीं है: अलग-अलग ऑब्जेक्ट्स का हैश कोड एक जैसा हो सकता है (collision)। \n
जब आप सिर्फ Equals को Override करते हैं, लेकिन GetHashCode को नहीं, तो आप System.Object से इनहेरिट की गई इम्प्लीमेंटेशन का उपयोग करते हैं, जो मेमोरी में रेफरेंस पर आधारित होती है। आपके तर्क के अनुसार "बराबर" दो ऑब्जेक्ट्स के हैश कोड अलग-अलग होंगे। और यहीं से समस्या शुरू होती है।
समस्या का उदाहरण: वे फ्लाइट्स जो डिक्शनरी में खो गईं
\n\nइस कोड को देखें। लगता है कि इसे काम करना चाहिए, लेकिन यह काम नहीं करता:
\n\npublic class Flight\n{\n public string FlightNumber { get; }\n public DateTime Date { get; }\n\n public Flight(string flightNumber, DateTime date)\n {\n FlightNumber = flightNumber;\n Date = date;\n }\n\n public override bool Equals(object obj)\n {\n if (obj is Flight other)\n return FlightNumber == other.FlightNumber && Date == other.Date;\n return false;\n }\n \n // GetHashCode Override नहीं किया गया!\n}\n\n// उपयोग\nvar flights = new Dictionary<Flight, string>();\nvar f1 = new Flight("SU100", new DateTime(2024, 12, 25));\nvar f2 = new Flight("SU100", new DateTime(2024, 12, 25));\n\nflights[f1] = "मॉस्को - पेरिस";\n\n// गलती! f2 नहीं मिला, जबकि f1.Equals(f2) == true\nConsole.WriteLine(flights.ContainsKey(f2)); // False\n\nऐसा क्यों? f1 और f2 मेमोरी में अलग-अलग ऑब्जेक्ट्स हैं। Object.GetHashCode() से उनके हैश कोड अलग-अलग हैं। Dictionary f1 को बकेट नंबर 123 में रखती है, और f2 को बकेट नंबर 456 में ढूँढ़ती है। Equals कॉल ही नहीं होता! नतीजा — एलिमेंट नहीं मिला।
परफॉरमेंस की समस्या: जब सब कुछ काम करता है, लेकिन धीमा
\n\nसबसे बुरा आंशिक समाधान है। मान लीजिए आपने GetHashCode को Override तो किया, लेकिन बहुत खराब तरीके से, जैसे कि एक कॉन्स्टेंट लौटाना:
public override int GetHashCode() => 42; // सभी ऑब्जेक्ट्स एक ही बकेट में\n\nऔपचारिक रूप से अनुबंध पूरा हुआ (बराबर ऑब्जेक्ट्स का हैश एक जैसा है)। लेकिन परफॉरमेंस बुरी तरह गिर जाएगी: सभी ऑब्जेक्ट्स एक ही बकेट में आ जाएँगे, और Dictionary एक लिस्ट में बदल जाएगी। इंसर्शन और सर्च O(n) हो जाएँगे। 10,000 एलिमेंट्स वाली Dictionary के लिए यह आपदा है।
\n\n