एक IEqualityComparer में एक प्रतिनिधि लपेटें


127

कई Linq.Enumerable फ़ंक्शन एक लेते हैं IEqualityComparer<T>। क्या एक सुविधाजनक रैपर वर्ग है जो delegate(T,T)=>boolलागू करने के लिए अडाप्ट करता है IEqualityComparer<T>? यह एक को लिखना आसान है (यदि आपकी समस्याओं को एक सही हैशकोड को परिभाषित करने से अनदेखा किया गया है), लेकिन मैं यह जानना चाहूंगा कि क्या कोई आउट-ऑफ-द-बॉक्स समाधान है।

विशेष रूप से, मैं Dictionaryसदस्यता को परिभाषित करने के लिए केवल कुंजी का उपयोग करते हुए सेट पर ऑपरेशन करना चाहता हूं (विभिन्न नियमों के अनुसार मूल्यों को बनाए रखते हुए)।

जवाबों:


44

आमतौर पर, मैं इस उत्तर पर @Sam टिप्पणी करके इसे हल करवाऊंगा (मैंने व्यवहार में बदलाव किए बिना इसे थोड़ा साफ करने के लिए मूल पोस्ट पर कुछ संपादन किया है।)

निम्नलिखित [ सैम के जवाब के साथ, मेरी IMiffHO है] डिफ़ॉल्ट हैशिंग नीति के लिए महत्वपूर्ण फिक्स: -

class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    readonly Func<T, T, bool> _comparer;
    readonly Func<T, int> _hash;

    public FuncEqualityComparer( Func<T, T, bool> comparer )
        : this( comparer, t => 0 ) // NB Cannot assume anything about how e.g., t.GetHashCode() interacts with the comparer's behavior
    {
    }

    public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
    {
        _comparer = comparer;
        _hash = hash;
    }

    public bool Equals( T x, T y )
    {
        return _comparer( x, y );
    }

    public int GetHashCode( T obj )
    {
        return _hash( obj );
    }
}

5
जहां तक ​​मेरा सवाल है यह सही उत्तर है। जो भी बाहर IEqualityComparer<T>निकलता GetHashCodeहै वह सीधे-सीधे टूट जाता है।
दान ताओ

1
@ जोशुआ फ्रैंक: यह हैश समानता का उपयोग करने के लिए समानता का उपयोग करने के लिए मान्य नहीं है - केवल उलटा सच है। संक्षेप में, @ डान ताओ जो कहते हैं, उसमें वह पूरी तरह से सही है, और यह उत्तर केवल इस तथ्य का एक पूर्व अपूर्ण उत्तर के लिए आवेदन है
रूबेन बार्टेलिंक

2
@ रूबेन बार्टलिंक: स्पष्ट करने के लिए धन्यवाद। लेकिन मैं अभी भी t => 0. की अपनी हैशिंग नीति को नहीं समझता हूँ। यदि सभी वस्तुओं में हमेशा एक ही चीज़ (शून्य) हैश की जाती है, तो क्या यह @ oban.ashetash.code के अनुसार @Dan ताओ की बात का उपयोग करने से ज्यादा टूटी हुई है? हमेशा एक अच्छा हैश फ़ंक्शन प्रदान करने के लिए कॉलर को मजबूर क्यों नहीं किया जाता है?
जोशुआ फ्रैंक

1
इस प्रकार यह मान लेना उचित नहीं है कि एक Func में एक मनमाना एल्गोरिथ्म इसकी आपूर्ति की गई है, संभवतः हैश कोड अलग होने के बावजूद सही नहीं लौट सकता है। आपका कहना है कि हर समय शून्य पर लौटना हशिंग नहीं है। यही कारण है कि एक अधिभार है कि हैशिंग फंक लेता है जब प्रोफाइलर हमें बताता है कि खोज पर्याप्त रूप से कुशल नहीं है। इस सब में एकमात्र बिंदु यह है कि यदि आप एक डिफ़ॉल्ट हैशिंग एल्गोरिथ्म के लिए जा रहे हैं, तो यह एक ऐसा होना चाहिए जो 100% समय पर काम करता है और खतरनाक सतही रूप से सही व्यवहार नहीं करता है। और फिर हम प्रदर्शन पर काम कर सकते हैं!
बारबेलिंक

4
दूसरे शब्दों में, जब से तुम एक प्रयोग कर रहे हैं कस्टम यह वस्तु के साथ कोई संबंध नहीं है comparer डिफ़ॉल्ट हैश से संबंधित कोड डिफ़ॉल्ट comparer, इस प्रकार आप इसका इस्तेमाल नहीं कर सकते।
Peet Brits

170

के महत्व पर 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 के उत्तर पर विस्तार :

यहां कुछ सुधार किए जा सकते हैं।

  1. पहले, मैं Func<T, TKey>इसके बजाय ले जाऊँगा Func<T, object>; यह वास्तविक में keyExtractorही मूल्य प्रकार की कुंजियों के बॉक्सिंग को रोक देगा ।
  2. दूसरा, मैं वास्तव में एक 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));
    }
}

1
आपकी StrictKeyEqualityComparer.Equalsविधि समान प्रतीत होती है KeyEqualityComparer.Equals। क्या TKey : IEquatable<TKey>बाधा TKey.Equalsकाम को अलग तरह से बनाती है?
जस्टिन मॉर्गन

2
@JustinMorgan: हाँ - पहले मामले में, चूंकि TKeyकोई भी मनमाना प्रकार हो सकता है, कंपाइलर वर्चुअल विधि का उपयोग करेगा, Object.Equalsजिसके लिए मूल्य प्रकार के मापदंडों की बॉक्सिंग की आवश्यकता होगी, जैसे int,। हालांकि, बाद के मामले में, क्योंकि TKeyइसे लागू करने के लिए विवश किया जाता है IEquatable<TKey>, इस TKey.Equalsपद्धति का उपयोग किया जाएगा जिसके लिए किसी मुक्केबाजी की आवश्यकता नहीं होगी।
डान ताओ

2
बहुत दिलचस्प, जानकारी के लिए धन्यवाद। मुझे नहीं पता था GetHashCode इन जवाबों को देखने तक इन LINQ निहितार्थ थे। भविष्य के उपयोग के लिए बहुत अच्छा है।
जस्टिन मॉर्गन

1
@ जोहान्सहः शायद! की जरूरत को StringKeyEqualityComparer<T, TKey>भी खत्म कर दिया होता ।
दान ताओ Dan

1
+1 @ डानटैओ: नेट में समानता को परिभाषित करते समय किसी को कभी भी हैश कोड को अनदेखा नहीं करना चाहिए।
मार्सेलो कैंटोस

118

जब आप समानता की जाँच को अनुकूलित करना चाहते हैं, तो उस समय की 99% आप अपनी तुलना करने के लिए कुंजियों को परिभाषित करने में रुचि रखते हैं, न कि खुद की तुलना करने के लिए।

यह एक सुरुचिपूर्ण समाधान हो सकता है (पायथन की सूची प्रकार विधि से अवधारणा )।

उपयोग:

var foo = new List<string> { "abc", "de", "DE" };

// case-insensitive distinct
var distinct = foo.Distinct(new KeyEqualityComparer<string>( x => x.ToLower() ) );

KeyEqualityComparerवर्ग:

public class KeyEqualityComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, object> keyExtractor;

    public KeyEqualityComparer(Func<T,object> keyExtractor)
    {
        this.keyExtractor = keyExtractor;
    }

    public bool Equals(T x, T y)
    {
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }

    public int GetHashCode(T obj)
    {
        return this.keyExtractor(obj).GetHashCode();
    }
}

3
यह औकात के जवाब से काफी बेहतर है।
SLKs

निश्चित रूप से सही दृष्टिकोण। मेरी राय में कुछ सुधार किए जा सकते हैं, जिनका मैंने अपने उत्तर में उल्लेख किया है।
दान ताओ

1
यह बहुत ही सुंदर कोड है, लेकिन यह सवाल का जवाब नहीं देता है, यही वजह है कि मैंने इसके बजाय @ अकु का जवाब स्वीकार किया। मैं फंक <टी, टी, बूल> के लिए एक आवरण चाहता था और मुझे कुंजी निकालने की कोई आवश्यकता नहीं है, क्योंकि कुंजी पहले से ही मेरे शब्दकोश में अलग हो गई है।
मार्सेलो कैंटोस

6
@ मार्सेलो: यह ठीक है, आप ऐसा कर सकते हैं; लेकिन ध्यान रखें कि आप @ अकु का दृष्टिकोण लेने के लिए जा रहे हैं हो सकता है, तो आप वास्तव में चाहिए एक जोड़ने के Func<T, int>एक के लिए हैश कोड की आपूर्ति करने Tमूल्य (के रूप में सुझाव दिया गया है, जैसे, रूबेन के जवाब )। अन्यथा IEqualityComparer<T>आपके द्वारा छोड़ा गया कार्यान्वयन काफी टूट गया है, विशेष रूप से LINQ एक्सटेंशन विधियों में इसकी उपयोगिता के संबंध में। यह क्यों है, इस पर चर्चा के लिए मेरा उत्तर देखें।
दान ताओ

यह अच्छा है, लेकिन यदि कुंजी का चयन किया जा रहा है तो एक मूल्य प्रकार होगा जिसमें अनावश्यक मुक्केबाजी होगी। शायद कुंजी को परिभाषित करने के लिए एक TKey होना बेहतर होगा।
ग्राहम एम्ब्रोज़

48

मुझे डर है कि ऐसा कोई रैपर आउट ऑफ बॉक्स न हो। हालाँकि इसे बनाना मुश्किल नहीं है:

class Comparer<T>: IEqualityComparer<T>
{
    private readonly Func<T, T, bool> _comparer;

    public Comparer(Func<T, T, bool> comparer)
    {
        if (comparer == null)
            throw new ArgumentNullException("comparer");

        _comparer = comparer;
    }

    public bool Equals(T x, T y)
    {
        return _comparer(x, y);
    }

    public int GetHashCode(T obj)
    {
        return obj.ToString().ToLower().GetHashCode();
    }
}

...

Func<int, int, bool> f = (x, y) => x == y;
var comparer = new Comparer<int>(f);
Console.WriteLine(comparer.Equals(1, 1));
Console.WriteLine(comparer.Equals(1, 2));

1
हालाँकि, GetHashCode के कार्यान्वयन के साथ सावधान रहें। यदि आप वास्तव में इसे किसी प्रकार की हैश तालिका में उपयोग करने जा रहे हैं, तो आप कुछ अधिक मजबूत चाहते हैं।
thecoop

46
इस कोड में एक गंभीर समस्या है! एक वर्ग के साथ आना आसान है जिसमें दो ऑब्जेक्ट हैं जो इस तुलना के संदर्भ में समान हैं, लेकिन अलग-अलग हैश कोड हैं।
एम्पि

10
इसे मापने के लिए, कक्षा को एक अन्य सदस्य की आवश्यकता होती private readonly Func<T, int> _hashCodeResolverहै जिसे निर्माणकर्ता में भी पास होना चाहिए और GetHashCode(...)विधि में उपयोग किया जाना चाहिए ।
हर्ज़मीिस्टर

6
मैं उत्सुक हूं: आप obj.ToString().ToLower().GetHashCode()इसके बजाय क्यों इस्तेमाल कर रहे हैं obj.GetHashCode()?
जस्टिन मॉर्गन

3
फ्रेमवर्क में वे IEqualityComparer<T>स्थान जो पर्दे के पीछे हैशिंग का उपयोग करते हैं (उदाहरण के लिए, LINQ के GroupBy, डिस्टिंक्ट, को छोड़कर, जॉइन, आदि) और हैशिंग के बारे में एमएस अनुबंध इस कार्यान्वयन में टूट गया है। यहाँ MS का दस्तावेज़ीकरण प्रस्तुत किया गया है: "कार्यान्वयन यह सुनिश्चित करने के लिए आवश्यक है कि यदि समान विधि दो वस्तुओं x और y के लिए सही हो, तो GetHashCode विधि द्वारा x के लिए लौटाया गया मान y के लिए लौटाए गए मान के बराबर होना चाहिए।" देखें: msdn.microsoft.com/en-us/library/ms132155
devgeezer

22

दान ताओ के उत्तर के रूप में भी, लेकिन कुछ सुधारों के साथ:

  1. EqualityComparer<>.Defaultवास्तविक तुलना करने पर निर्भर करता है ताकि यह मूल्य प्रकारों के लिए बॉक्सिंग से बचा जाए जो इसे structलागू किया गया है IEquatable<>

  2. चूंकि EqualityComparer<>.Defaultइसका उपयोग नहीं किया गया है null.Equals(something)

  3. बशर्ते स्थिर रैपर IEqualityComparer<>जिसके चारों ओर तुलना की आवृत्ति बनाने के लिए एक स्थिर विधि होगी - कॉलिंग को आसान बनाता है। तुलना

    Equality<Person>.CreateComparer(p => p.ID);

    साथ में

    new EqualityComparer<Person, int>(p => p.ID);
  4. IEqualityComparer<>कुंजी के लिए निर्दिष्ट करने के लिए एक अधिभार जोड़ा गया ।

कक्षा:

public static class Equality<T>
{
    public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector)
    {
        return CreateComparer(keySelector, null);
    }

    public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector, 
                                                         IEqualityComparer<V> comparer)
    {
        return new KeyEqualityComparer<V>(keySelector, comparer);
    }

    class KeyEqualityComparer<V> : IEqualityComparer<T>
    {
        readonly Func<T, V> keySelector;
        readonly IEqualityComparer<V> comparer;

        public KeyEqualityComparer(Func<T, V> keySelector, 
                                   IEqualityComparer<V> comparer)
        {
            if (keySelector == null)
                throw new ArgumentNullException("keySelector");

            this.keySelector = keySelector;
            this.comparer = comparer ?? EqualityComparer<V>.Default;
        }

        public bool Equals(T x, T y)
        {
            return comparer.Equals(keySelector(x), keySelector(y));
        }

        public int GetHashCode(T obj)
        {
            return comparer.GetHashCode(keySelector(obj));
        }
    }
}

आप इसे इस तरह उपयोग कर सकते हैं:

var comparer1 = Equality<Person>.CreateComparer(p => p.ID);
var comparer2 = Equality<Person>.CreateComparer(p => p.Name);
var comparer3 = Equality<Person>.CreateComparer(p => p.Birthday.Year);
var comparer4 = Equality<Person>.CreateComparer(p => p.Name, StringComparer.CurrentCultureIgnoreCase);

व्यक्ति एक साधारण वर्ग है:

class Person
{
    public int ID { get; set; }
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
}

3
एक कार्यान्वयन प्रदान करने के लिए +1 जो आपको कुंजी के लिए एक तुलना प्रदान करता है। अधिक लचीलापन देने के साथ-साथ यह तुलना और हैशिंग दोनों के लिए मुक्केबाजी मूल्य प्रकार से भी बचता है।
devgeezer 3

2
यह यहाँ सबसे अधिक जवाब दिया गया है। मैंने एक शून्य चेक भी जोड़ा। पूर्ण।
नवफ़ल

11
public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    readonly Func<T, T, bool> _comparer;
    readonly Func<T, int> _hash;

    public FuncEqualityComparer( Func<T, T, bool> comparer )
        : this( comparer, t => t.GetHashCode())
    {
    }

    public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
    {
        _comparer = comparer;
        _hash = hash;
    }

    public bool Equals( T x, T y )
    {
        return _comparer( x, y );
    }

    public int GetHashCode( T obj )
    {
        return _hash( obj );
    }
}

एक्सटेंशन के साथ: -

public static class SequenceExtensions
{
    public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer )
    {
        return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer ) );
    }

    public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer, Func<T, int> hash )
    {
        return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer, hash ) );
    }
}

@Sam (जो अब इस टिप्पणी के रूप में मौजूद नहीं है): बिना किसी व्यवहार के कोड को साफ कर दिया (और + 1 +1)। पर जोड़ा गया Riff stackoverflow.com/questions/98033/...
रूबेन Bartelink

6

orip का जवाब बहुत अच्छा है।

यहाँ थोड़ा विस्तार विधि इसे और भी आसान बनाने के लिए है:

public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, object>    keyExtractor)
{
    return list.Distinct(new KeyEqualityComparer<T>(keyExtractor));
}
var distinct = foo.Distinct(x => x.ToLower())

2

मैं अपने सवाल का जवाब देने जा रहा हूं। डिस्क्स को सेट के रूप में मानने के लिए, सबसे सरल तरीका यह है कि सेट ऑपरेशंस को डिक्टेट करने के लिए।


2

कार्यान्वयन (जर्मन पाठ) कार्यान्वयन lambda अभिव्यक्ति के साथ IEqualityCompare शून्य मानों की परवाह करता है और IEqualityComparer उत्पन्न करने के लिए विस्तार विधियों का उपयोग करता है।

एक Linqu संघ में IEqualityComparer बनाने के लिए आपको बस लिखना होगा

persons1.Union(persons2, person => person.LastName)

तुलना:

public class LambdaEqualityComparer<TSource, TComparable> : IEqualityComparer<TSource>
{
  Func<TSource, TComparable> _keyGetter;

  public LambdaEqualityComparer(Func<TSource, TComparable> keyGetter)
  {
    _keyGetter = keyGetter;
  }

  public bool Equals(TSource x, TSource y)
  {
    if (x == null || y == null) return (x == null && y == null);
    return object.Equals(_keyGetter(x), _keyGetter(y));
  }

  public int GetHashCode(TSource obj)
  {
    if (obj == null) return int.MinValue;
    var k = _keyGetter(obj);
    if (k == null) return int.MaxValue;
    return k.GetHashCode();
  }
}

आपको प्रकार के अनुमान का समर्थन करने के लिए एक विस्तार विधि भी जोड़ने की आवश्यकता है

public static class LambdaEqualityComparer
{
       // source1.Union(source2, lambda)
        public static IEnumerable<TSource> Union<TSource, TComparable>(
           this IEnumerable<TSource> source1, 
           IEnumerable<TSource> source2, 
            Func<TSource, TComparable> keySelector)
        {
            return source1.Union(source2, 
               new LambdaEqualityComparer<TSource, TComparable>(keySelector));
       }
   }

1

केवल एक अनुकूलन: हम इसे तुलना करने के बजाय मूल्य तुलना के लिए आउट-ऑफ-द-बॉक्स इक्वलिटीकॉमपर का उपयोग कर सकते हैं।

यह कार्यान्वयन क्लीनर को भी बनाएगा क्योंकि वास्तविक तुलना तर्क अब GetHashCode () और इक्वल्स () में रहता है जिन्हें आपने पहले ही अधिभारित कर लिया होगा।

यहाँ कोड है:

public class MyComparer<T> : IEqualityComparer<T> 
{ 
  public bool Equals(T x, T y) 
  { 
    return EqualityComparer<T>.Default.Equals(x, y); 
  } 

  public int GetHashCode(T obj) 
  { 
    return obj.GetHashCode(); 
  } 
} 

अपनी वस्तु पर GetHashCode () और बराबरी () के तरीकों को ओवरलोड करना न भूलें।

इस पोस्ट से मुझे मदद मिली: c # दो सामान्य मूल्यों की तुलना करें

सुशील


1
NB एक ही मुद्दा जैसा कि stackoverflow.com/questions/98033/… पर टिप्पणी में पहचाना गया है - CANT मानें obj.GetHashCode () समझ में आता है
Ruben Bartelink

4
मुझे इसका उद्देश्य नहीं मिला। आपने एक समानता तुलनित्र बनाया है जो डिफ़ॉल्ट समानता तुलनित्र के बराबर है। तो आप इसे सीधे उपयोग क्यों नहीं करते?
कोडइन्चोस

1

orip का जवाब बहुत अच्छा है। Orip के उत्तर पर विस्तार:

मुझे लगता है कि समाधान की कुंजी "विस्तार विधि" का उपयोग "अनाम प्रकार" को स्थानांतरित करने के लिए है।

    public static class Comparer 
    {
      public static IEqualityComparer<T> CreateComparerForElements<T>(this IEnumerable<T> enumerable, Func<T, object> keyExtractor)
      {
        return new KeyEqualityComparer<T>(keyExtractor);
      }
    }

उपयोग:

var n = ItemList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList();
n.AddRange(OtherList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList(););
n = n.Distinct(x=>new{Vchr=x.Vchr,Id=x.Id}).ToList();

0
public static Dictionary<TKey, TValue> Distinct<TKey, TValue>(this IEnumerable<TValue> items, Func<TValue, TKey> selector)
  {
     Dictionary<TKey, TValue> result = null;
     ICollection collection = items as ICollection;
     if (collection != null)
        result = new Dictionary<TKey, TValue>(collection.Count);
     else
        result = new Dictionary<TKey, TValue>();
     foreach (TValue item in items)
        result[selector(item)] = item;
     return result;
  }

यह इस तरह से लंबोदर के साथ एक संपत्ति का चयन करना संभव बनाता है: .Select(y => y.Article).Distinct(x => x.ArticleID);


-2

मुझे एक मौजूदा वर्ग का पता नहीं है लेकिन कुछ इस तरह है:

public class MyComparer<T> : IEqualityComparer<T>
{
  private Func<T, T, bool> _compare;
  MyComparer(Func<T, T, bool> compare)
  {
    _compare = compare;
  }

  public bool Equals(T x, Ty)
  {
    return _compare(x, y);
  }

  public int GetHashCode(T obj)
  {
    return obj.GetHashCode();
  }
}

नोट: मैंने वास्तव में इसे अभी तक संकलित और नहीं चलाया है, इसलिए कोई टाइपो या अन्य बग हो सकता है।


1
NB एक ही मुद्दा जैसा कि stackoverflow.com/questions/98033/… पर टिप्पणी में पहचाना गया है - CANT मानें obj.GetHashCode () समझ में आता है
Ruben Bartelink
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.