के महत्व पर GetHashCode
दूसरों ने पहले से ही इस तथ्य पर टिप्पणी की है कि किसी भी कस्टम IEqualityComparer<T>
कार्यान्वयन में वास्तव में एक GetHashCode
विधि शामिल होनी चाहिए ; लेकिन किसी ने भी यह बताने की जहमत नहीं उठाई कि किसी भी विस्तार में क्यों ।
यहाँ पर क्यों। आपके प्रश्न में विशेष रूप से LINQ एक्सटेंशन विधियों का उल्लेख है; इनमें से लगभग सभी हैश कोड पर ठीक से काम करने के लिए भरोसा करते हैं, क्योंकि वे दक्षता के लिए आंतरिक रूप से हैश टेबल का उपयोग करते हैं।
Distinct
उदाहरण के लिए लीजिए । इस विस्तार विधि के निहितार्थों पर विचार करें यदि इसका उपयोग किया गया है तो यह एक Equals
विधि है। आप कैसे निर्धारित करते हैं कि क्या किसी वस्तु का पहले से ही अनुक्रम में स्कैन किया गया है यदि आपके पास केवल है Equals
? आप उन मूल्यों के पूरे संग्रह पर भरोसा करते हैं जो आपने पहले ही देख चुके हैं और एक मैच की जांच कर रहे हैं। यह एक O (N) एक के बजाय Distinct
सबसे खराब स्थिति O (N 2 ) एल्गोरिथ्म का उपयोग करने के परिणामस्वरूप होगा !
सौभाग्य से, यह मामला नहीं है। सिर्फ उपयोग Distinct
नहीं करता है ; यह भी उपयोग करता है । वास्तव में, यह पूरी तरह से ठीक से काम नहीं करता है कि एक उचित आपूर्ति करता हैEquals
GetHashCode
IEqualityComparer<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
है वह सीधे-सीधे टूट जाता है।