के महत्व पर GetHashCode
दूसरों ने पहले से ही इस तथ्य पर टिप्पणी की है कि किसी भी कस्टम IEqualityComparer<T>कार्यान्वयन में वास्तव में एक GetHashCodeविधि शामिल होनी चाहिए ; लेकिन किसी ने भी यह बताने की जहमत नहीं उठाई कि किसी भी विस्तार में क्यों ।
यहाँ पर क्यों। आपके प्रश्न में विशेष रूप से LINQ एक्सटेंशन विधियों का उल्लेख है; इनमें से लगभग सभी हैश कोड पर ठीक से काम करने के लिए भरोसा करते हैं, क्योंकि वे दक्षता के लिए आंतरिक रूप से हैश टेबल का उपयोग करते हैं।
Distinctउदाहरण के लिए लीजिए । इस विस्तार विधि के निहितार्थों पर विचार करें यदि इसका उपयोग किया गया है तो यह एक Equalsविधि है। आप कैसे निर्धारित करते हैं कि क्या किसी वस्तु का पहले से ही अनुक्रम में स्कैन किया गया है यदि आपके पास केवल है Equals? आप उन मूल्यों के पूरे संग्रह पर भरोसा करते हैं जो आपने पहले ही देख चुके हैं और एक मैच की जांच कर रहे हैं। यह एक O (N) एक के बजाय Distinctसबसे खराब स्थिति O (N 2 ) एल्गोरिथ्म का उपयोग करने के परिणामस्वरूप होगा !
सौभाग्य से, यह मामला नहीं है। सिर्फ उपयोग Distinctनहीं करता है ; यह भी उपयोग करता है । वास्तव में, यह पूरी तरह से ठीक से काम नहीं करता है कि एक उचित आपूर्ति करता हैEqualsGetHashCodeIEqualityComparer<T>GetHashCode । नीचे इस बात का उदाहरण दिया गया है।
कहो मेरे पास निम्न प्रकार है:
class Value
{
public string Name { get; private set; }
public int Number { get; private set; }
public Value(string name, int number)
{
Name = name;
Number = number;
}
public override string ToString()
{
return string.Format("{0}: {1}", Name, Number);
}
}
अब कहते हैं कि मेरे पास एक है List<Value>और मैं सभी तत्वों को एक अलग नाम से खोजना चाहता हूं। यह Distinctकस्टम समानता तुलनित्र का उपयोग करने के लिए एक सही उपयोग मामला है । तो चलिए अकु के उत्तरComparer<T> से कक्षा का उपयोग करते हैं :
var comparer = new Comparer<Value>((x, y) => x.Name == y.Name);
अब, यदि हमारे पास Valueसमान Nameसंपत्ति वाले तत्वों का एक समूह है , तो उन्हें सभी को एक मूल्य में वापस करना चाहिए Distinct, है ना? चलो देखते हैं...
var values = new List<Value>();
var random = new Random();
for (int i = 0; i < 10; ++i)
{
values.Add("x", random.Next());
}
var distinct = values.Distinct(comparer);
foreach (Value x in distinct)
{
Console.WriteLine(x);
}
आउटपुट:
x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377
हम्म, यह काम नहीं किया, यह किया?
किस बारे में GroupBy? आइए कोशिश करते हैं कि:
var grouped = values.GroupBy(x => x, comparer);
foreach (IGrouping<Value> g in grouped)
{
Console.WriteLine("[KEY: '{0}']", g);
foreach (Value x in g)
{
Console.WriteLine(x);
}
}
आउटपुट:
[कुंजी = 'x: 1346013431']
x: 1346013431
[कुंजी = 'x: 1388845717']
x: 1388845717
[कुंजी = 'x: 1576754134']
x: 1576754134
[कुंजी = 'x: 1104067189']
x: 1104067189
[कुंजी = 'x: 1144789201']
x: 1144789201
[कुंजी = 'x: 1862076501']
x: 1862076501
[कुंजी = 'x: 1573781440']
x: 1573781440
[कुंजी = 'x: 646797592']
x: 646797592
[कुंजी = 'x: 655632802']
x: 655632802
[कुंजी = 'x: 1206819377']
x: 1206819377
फिर से: काम नहीं किया
आप इसके बारे में सोचते हैं, यह भावना बनाना होगा के लिए Distinctएक का उपयोग करने के HashSet<T>(या समतुल्य) आंतरिक रूप से, और के लिए GroupByएक तरह उपयोग करने के लिए कुछ Dictionary<TKey, List<T>>आंतरिक रूप से। क्या यह समझा सकता है कि ये तरीके काम क्यों नहीं करते? चलो यह करके देखें:
var uniqueValues = new HashSet<Value>(values, comparer);
foreach (Value x in uniqueValues)
{
Console.WriteLine(x);
}
आउटपुट:
x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377
हाँ ... समझ बनाने के लिए शुरू?
इन उदाहरणों से उम्मीद है कि यह स्पष्ट है कि GetHashCodeकिसी भी IEqualityComparer<T>कार्यान्वयन में एक उपयुक्त सहित इतना महत्वपूर्ण क्यों है।
मूल उत्तर
Orip के उत्तर पर विस्तार :
यहां कुछ सुधार किए जा सकते हैं।
- पहले, मैं
Func<T, TKey>इसके बजाय ले जाऊँगा Func<T, object>; यह वास्तविक में keyExtractorही मूल्य प्रकार की कुंजियों के बॉक्सिंग को रोक देगा ।
- दूसरा, मैं वास्तव में एक
where TKey : IEquatable<TKey>बाधा जोड़ूंगा; यह Equalsकॉल में बॉक्सिंग को रोक देगा ( object.Equalsएक objectपैरामीटर लेता है ; आपको इसे बॉक्सिंग के बिना IEquatable<TKey>एक TKeyपैरामीटर लेने के लिए कार्यान्वयन की आवश्यकता है)। स्पष्ट रूप से यह बहुत गंभीर प्रतिबंध हो सकता है, इसलिए आप बाधा के बिना एक आधार वर्ग और इसके साथ एक व्युत्पन्न वर्ग बना सकते हैं।
यहाँ परिणामी कोड कैसा दिख सकता है:
public class KeyEqualityComparer<T, TKey> : IEqualityComparer<T>
{
protected readonly Func<T, TKey> keyExtractor;
public KeyEqualityComparer(Func<T, TKey> keyExtractor)
{
this.keyExtractor = keyExtractor;
}
public virtual bool Equals(T x, T y)
{
return this.keyExtractor(x).Equals(this.keyExtractor(y));
}
public int GetHashCode(T obj)
{
return this.keyExtractor(obj).GetHashCode();
}
}
public class StrictKeyEqualityComparer<T, TKey> : KeyEqualityComparer<T, TKey>
where TKey : IEquatable<TKey>
{
public StrictKeyEqualityComparer(Func<T, TKey> keyExtractor)
: base(keyExtractor)
{ }
public override bool Equals(T x, T y)
{
// This will use the overload that accepts a TKey parameter
// instead of an object parameter.
return this.keyExtractor(x).Equals(this.keyExtractor(y));
}
}
IEqualityComparer<T>निकलताGetHashCodeहै वह सीधे-सीधे टूट जाता है।