सम

समानता में C#

5 अभ्यास

समानता के बारे में

कोडिंग अभ्यास C# में समानता के कई गुण दिखाता है:

Object.Equals()

कोडिंग अभ्यास में शामिल विषय

  • साधारण टाइप (स्ट्रिंग और प्रिमिटिव) की समानता की जाँच आम तौर पर == और != से की जाती है। इन टाइप के साथ उपलब्ध Equals() मेथड इस्तेमाल करने की तुलना में इसे ज़्यादा स्वाभाविक माना जाता है। Java प्रोग्रामर को स्ट्रिंग के साथ काम करते समय इस बात का ध्यान रखना चाहिए कि == C# में वैल्यू की तुलना करता है, जबकि अपनी पुरानी भाषा Java में लौटने पर वह रेफ़रेंस की तुलना करता है।
  • रेफ़रेंस टाइप (क्लास के इंस्टेंस) की तुलना object से विरासत में मिले Equals() मेथड से की जाती है। अगर समानता की जाँच का आपका उद्देश्य यह पक्का करना है कि दो ऑब्जेक्ट बिलकुल एक ही इंस्टेंस हैं, तो object के इम्प्लीमेंटेशन पर भरोसा करना काफ़ी है। अगर नहीं, तो आपको object.Equals() को ओवरराइड करना होगा।
  • अगर आप जानते हैं कि आपकी क्लास के सारे इंस्टेंस एक ही जगह बनाए जाते हैं, जैसे किसी गेम या सिमुलेशन के पात्र, तो रेफ़रेंस समानता काफ़ी है। लेकिन हो सकता है कि वास्तविक दुनिया की एक ही चीज़ के कई इंस्टेंस बनें (जैसे डेटाबेस से, उपयोगकर्ता के इनपुट से, या किसी वेब रिक्वेस्ट के ज़रिए)। ऐसे में जो वैल्यू उस चीज़ की विशिष्ट पहचान तय करती हैं, उनकी समानता जाँचनी होती है। इसलिए Equals() को ओवरराइड करना ज़रूरी है।
  • ओवरराइड किया गया Equals() साधारण टाइप के मेंबर की समानता की जाँच == से करता है और रेफ़रेंस टाइप की जाँच Equals() में रिकर्सिव कॉल से।
class StatusBar
{
    private readonly int width = 200, height = 20;

    public override bool Equals(object other)
    {
        // ... null and type checks and performance optimisations
        return width == (other as StatusBar).width && height == (other as StatusBar).height;
    }
}
class Window
{
    private readonly string title = "Main";
    private readonly StatusBar statusBar = new StatusBar();

    public override bool Equals(object other)
    {
        // ... null and type checks and performance optimisations
        return title == (other as Window).title && statusBar.Equals((other as Window).statusBar);
    }
}

  • स्टैटिक मेथड object.ReferenceEquals() का उपयोग दो ऑब्जेक्ट की तुलना करके यह पता लगाने के लिए किया जाता है कि वे एक ही इंस्टेंस हैं या नहीं। इससे स्पष्टता आती है, और जहाँ Equals() तथा == को ओवरराइड या ओवरलोड किया गया हो वहाँ यह ज़रूरी भी हो जाता है।
var winA = new Window(); // above code shows that all windows are equal
var winB = new Window();
ReferenceEquals(winA, winB);
// => false
var winC = winA;
ReferenceEquals(winA, winC);
// => true

अन्य विषय

  • public override bool Equals(object obj) के अलावा, IDE अक्सर protected bool Equals(FacialFeatures other) ओवरलोड भी बना देते हैं, जो इनहेरिटेंस के समय काम आता है। व्युत्पन्न क्लास बेस क्लास के Equals() को कॉल कर सकती है और फिर अपनी जाँच जोड़ सकती है।
  • == का इस्तेमाल तब तक न कीजिए जब तक कि आपने अपनी क्लास में == ऑपरेटर और Equals() मेथड को ओवरलोड न कर लिया हो (देखिए operator-overloading अभ्यास), या जब तक आपके लिए सिर्फ़ रेफ़रेंस का बराबर होना ही मायने न रखता हो।
  • structs में समानता की जाँच structs अभ्यास में की गई है।
  • tuples की समानता tuples अभ्यास में की गई है।
  • कई डेवलपर समानता के मेथड का इम्प्लीमेंटेशन अपने IDE पर ही छोड़ देते हैं, क्योंकि ये समानता की सारी बारीकियाँ संभाल लेते हैं। उदाहरण के लिए, JetBrains का RIDER (v 2020.1) एक क्लास के लिए ये समानता मेथड बनाता है:
protected bool Equals(T other)
{
    return field1 == other.field1 && field2.Equals(other.field2);
}

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    if (obj.GetType() != this.GetType()) return false;
    return Equals((T) obj);
}
  • अगर आप अपने IDE के बनाए कोड को बेहतर बनाने का फैसला करें तो सावधान रहिए। वह कोड आम तौर पर null और गलत टाइप के ऑब्जेक्ट के खिलाफ़ पूरी तरह सुरक्षित रहता है और काफ़ी अच्छी तरह ऑप्टिमाइज़ भी होता है।
  • delegate की समानता की जाँच पर इस अभ्यास में खास चर्चा नहीं की गई है।
  • ऐरे और ज़्यादातर कलेक्शन के लिए कोई बिल्ट-इन समानता जाँच नहीं है। LINQ (जिसकी चर्चा आगे के अभ्यासों में है) SequenceEquals() देता है, लेकिन LINQ के बिना दोनों कलेक्शन पर इटरेशन करके हर आइटम की अलग-अलग तुलना करनी पड़ती है।
  • अपनी क्लास के साथ == और != कैसे इस्तेमाल करें, इसकी चर्चा operator-overloading अभ्यास में देखिए।

Object.GetHashCode()

  • object.GetHashCode() 32 बिट के पूर्णांक के रूप में हैश कोड लौटाता है। डिक्शनरी और सेट क्लास, जैसे Dictionary<T> और HashSet<T>, इस हैश कोड का उपयोग ऑब्जेक्ट को तेज़ी से संग्रहीत करने और निकालने के लिए करती हैं। डिक्शनरी के मामले में हैशिंग का संबंध की (key) से होता है।
  • C# डेवलपर यह अपेक्षा करते हैं कि अगर आप Equals() को ओवरराइड करते हैं तो GetHashCode() को भी ओवरराइड करेंगे। Equals() और GetHashCode() के बीच एक ऐसा संबंध है जो डिक्शनरी और हैश सेट क्लास तथा हैश कोड इस्तेमाल करने वाली अन्य क्लास के सही व्यवहार के लिए सही होना ज़रूरी है। आपसे अपेक्षा है कि आप यह मेथड इस तरह लागू करें कि भविष्य में हैश कोड पर आधारित कलेक्शन जोड़ने वाले मेंटेनर के लिए कोई जाल न बिछे।
  • हैश कोड और समानता के बीच संबंध यह है कि अगर दो ऑब्जेक्ट बराबर हैं (Equal() सही लौटाता है) तो दोनों ऑब्जेक्ट का GetHashCode() एक ही वैल्यू लौटाना चाहिए। उलटी दिशा में यह लागू नहीं होता। यह संबंध दोनों तरफ़ से नहीं है। सोचिए एक लुकअप फंक्शन की, जो पहले हैश कोड के आधार पर किसी "बकेट" में जाता है और फिर समानता की जाँच से वह खास आइटम चुन लेता है।
  • हैश कोड बनाने का सबसे आसान तरीका है HashCode.Combine() को कॉल करना और उसमें वे वैल्यू पास करना जो समानता की जाँच में इस्तेमाल होती हैं (या उनका कोई हिस्सा)। ध्यान रखिए कि Combine() को आप जितनी ज़्यादा जानकारी देंगे, हैश का इम्प्लीमेंटेशन उतना ही तेज़ होने की संभावना है।
public class Assessment
{
    private int rating;
    private Person boss;

    public override int GetHashCode()
    {
        return HashCode.Combine(rating, boss);
    }
}
  • जिन वैल्यू का उपयोग समानता की जाँच में होता है, वे तब तक स्थिर रहनी चाहिए जब तक हैश वाला कलेक्शन इस्तेमाल में है। अगर आप किसी ऑब्जेक्ट को एक वैल्यू के सेट के साथ कलेक्शन में जोड़ते हैं और फिर वे वैल्यू बदल देते हैं, तो हैश कोड अब सही "बकेट" की ओर संकेत नहीं करेगा। व्यवहार में इसका मतलब है कि ऑब्जेक्ट इम्यूटेबल होना चाहिए। दूसरे तरीकों में मेंटेनर के लिए मुश्किलें पैदा होने का खतरा रहता है। इम्यूटेबिलिटी पर दूसरे अभ्यासों में चर्चा की गई है।
  • यह मुमकिन है कि आप लाइब्रेरी के रूटीन से बेहतर हैश कोड बना सकें, लेकिन यह तभी होगा जब आपको डेटा की खास विशेषताओं की गहरी समझ हो, या जब कलेक्शन इतना सरल हो कि वैल्यू को बिना हैशिंग के सीधे इस्तेमाल किया जा सके। इतनी अतिरिक्त मेहनत करना शायद फ़ायदेमंद न हो।

परफ़ॉर्मेंस सुधार

परफ़ॉर्मेंस को थोड़ा बेहतर बनाने के लिए, खास तौर पर जब ऑब्जेक्ट किसी कलेक्शन का हिस्सा हों, आप एक ओवरलोडेड मेंबर public bool Equals(T other) जोड़ सकते हैं।

इससे रेफ़रेंस टाइप के लिए कुछ टाइप जाँच बच जाती है और वैल्यू टाइप के लिए बॉक्सिंग का एक चरण बच जाता है, क्योंकि उन्हें public override bool Equals(object other) के आर्गुमेंट के रूप में ऑब्जेक्ट में बदलने (बॉक्स करने) की ज़रूरत नहीं पड़ती।

अगर आप अपनी क्लास में IEquatable<T> इंटरफ़ेस जोड़ते हैं तो यह ओवरलोड लागू करना ज़रूरी हो जाता है। जब तक आपके कोड में ऐसे रूटीन न हों जो IEquatable<T> टाइप के ऑब्जेक्ट लेते हों (और इम्प्लीमेंट करने वाली क्लास से परे, शायद समानता से जुड़ा कुछ दिलचस्प काम करते हों), इंटरफ़ेस शामिल करने की और कोई खास वजह नहीं है।

IEqualityComparer<T>

अगर आपकी कोई क्लास दो अलग-अलग तरीकों से विशिष्ट रूप से पहचानी जा सकती है, जैसे Person क्लास जिसका एक SSID और एक अलग ईमेल पता है, तो .NET ऐसा तरीका देता है जिससे दो अलग-अलग कलेक्शन अलग-अलग हैश कोड और समानता की जाँच इस्तेमाल कर सकते हैं। हर कलेक्शन के पास IEqualityComparer<T> का अपना अलग इम्प्लीमेंटेशन हो सकता है, जिसमें अपना Equals() और GetHashCode() मेथड हो। आपके पास एक डिक्शनरी हो सकती है जो SSID पर आधारित हो और दूसरी जो ईमेल पते पर।

जहाँ IEqualityComparer<T> इस्तेमाल हो रहा हो, वहाँ भी आम तौर पर आप अपनी आइटम क्लास पर Equals() और GetHashCode() को ओवरराइड करेंगे, ताकि कलेक्शन क्लास के बाहर दिक्कतें न हों।

IEqualityComparer<T> इस्तेमाल करते समय एक बात ध्यान में रखनी होती है: प्राइवेट मेथड आदि समानता की जाँच के लिए उपलब्ध नहीं रहेंगे।

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

फ़्लोटिंग-पॉइंट समानता पर एक टिप्पणी

एक प्रिमिटिव जो लापरवाह कोडर के लिए चुनौती बन सकता है, वह है फ़्लोटिंग-पॉइंट वैल्यू की समानता की जाँच। इस पर floating-point-numbers कॉन्सेप्ट के about.md दस्तावेज़ में चर्चा की गई है।

समानता और इनहेरिटेंस

यह लेख उन कुछ फैसलों को दिखाता है जो समानता और इनहेरिटेंस के संबंध में लेने पड़ते हैं। ये आपके लिए तभी मायने रखेंगे जब आप किसी क्लास और उससे व्युत्पन्न क्लास के इंस्टेंस पर काम कर रहे हों और दोनों के बीच समानता जाँचनी हो।

GitHub के ज़रिए संपादित करें यह लिंक नई विंडो या टैब में खुलता है।

समानता सीखिए

अभ्यास लॉक है

समानता पर अभ्यास करने के लिए 4 और अभ्यास अनलॉक कीजिए