2013-04-06 5 views
6

मेरे पास कई कक्षाएं हैं जो कक्षा BaseClass से प्राप्त होती हैं जहां BaseClass में केवल एक आईडी आईडी है।बेस क्लास IEqualityComparer का उपयोग करके अलग करना() और अभी भी बाल वर्ग के प्रकार को वापस कर सकते हैं?

अब मुझे इन वस्तुओं में से कुछ के संग्रह पर अलग-अलग करने की आवश्यकता है।

public class PositionComparer : IEqualityComparer<Position> 
{ 
    public bool Equals(Position x, Position y) 
    { 
     return (x.Id == y.Id); 
    } 

    public int GetHashCode(Position obj) 
    { 
     return obj.Id.GetHashCode(); 
    } 
} 

को देखते हुए तर्क सिर्फ Id पर आधारित है, मैं एक ही comparer बनाई करना चाहता था दोहराव को कम करने के लिए:

public class BaseClassComparer : IEqualityComparer<BaseClass> 
{ 
    public bool Equals(BaseClass x, BaseClass y) 
    { 
     return (x.Id == y.Id); 
    } 

    public int GetHashCode(BaseClass obj) 
    { 
     return obj.Id.GetHashCode(); 
    } 
} 

लेकिन मैं निम्नलिखित कोड अधिक से अधिक बच्चे कक्षाओं से प्रत्येक के लिए है इस संकलन प्रतीत होता है नहीं:

IEnumerable<Position> positions = GetAllPositions(); 
    positions = allPositions.Distinct(new BaseClassComparer()) 

... के रूप में यह कहते हैं कि यह BaseClass से Position को परिवर्तित नहीं कर सकते। तुलनाकर्ता इस Distinct() कॉल के वापसी मूल्य को क्यों मजबूर करता है?

उत्तर

5

यदि आप Distinct की परिभाषा को देखते हैं तो केवल एक सामान्य प्रकार पैरामीटर शामिल है (और एक टीसीलेक्शन इनपुट और आउटपुट संग्रह और तुलनाकर्ता के लिए एक टीकॉम्पिसन के लिए उपयोग नहीं किया जाता है)। इसका मतलब है कि आपका बेस क्लास कॉम्पैयर परिणाम प्रकार को बेस क्लास में बाध्य करता है और असाइनमेंट पर रूपांतरण संभव नहीं है।

आप संभवतः एक सामान्य पैरामीटर के साथ जेनेरिककंपियर बना सकते हैं जो कि कम से कम बेस क्लास होने के लिए बाध्य है जो आपको जो करने की कोशिश कर रहा है उसके करीब हो सकता है। इस तरह

public class GenericComparer<T> : IEqualityComparer<T> where T : BaseClass 
{ 
    public bool Equals(T x, T y) 
    { 
     return x.Id == y.Id; 
    } 

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

लगेगा क्योंकि आप एक उदाहरण और न सिर्फ एक विधि की जरूरत है आप नहीं कर सकते कॉल सामान्य प्रकार संकलक (see this discussion) द्वारा अनुमान लगाया जा, लेकिन जब उदाहरण बनाकर ऐसा करने के लिए है:

IEnumerable<Position> positions; 
positions = allPositions.Distinct(new GenericComparer<Position>()); 

Eric's answer पूरे मुद्दे के मूल कारण (कॉन्वर्सिस और contravariance के मामले में) बताता है।

1

कल्पना कीजिए अगर आप था:

var positions = allPositions.Distinct(new BaseClassComparer()); 

क्या आप positions के प्रकार के होने की अपेक्षा करेंगे? चूंकि संकलक Distinct पर दिए गए तर्क से घटा देता है जो IEqualityComparer<BaseClass> लागू करता है, अभिव्यक्ति का प्रकार IEnumerable<BaseClass> है।

उस प्रकार को स्वचालित रूप से IEnumerable<Position> में परिवर्तित नहीं किया जा सकता है इसलिए संकलक एक त्रुटि उत्पन्न करता है।

IEnumerable<Position> distinct = positions.Distinct<Position>(new BaseClassComparer()); 

आप इस निर्दिष्ट नहीं करते हैं, संकलक infers: यदि आप Distinct के लिए जेनेरिक पैरामीटर निर्दिष्ट

0

IEqualityComparer<T> के बाद प्रकार T में contravariant है, तो आप अलग से आधार वर्ग comparer उपयोग कर सकते हैं TBaseClassBaseClassComparerIEqualityComparer<BaseClass> लागू करता है।

0

आपको अपने कोड में छोटे बदलावों की आवश्यकता है।

public class BaseClass 
{ 
    public int Id{get;set;} 
} 

public class Position : BaseClass 
{ 
    public string Name {get;set;} 
} 
public class Comaprer<T> : IEqualityComparer<T> 
    where T:BaseClass 
{ 

    public bool Equals(T x, T y) 
    { 
     return (x.Id == y.Id); 
    } 

    public int GetHashCode(T obj) 
    { 
     return obj.Id.GetHashCode(); 
    } 
} 
class Program 
{ 
    static void Main(string[] args) 
    { 
     List<Position> all = new List<Position> { new Position { Id = 1, Name = "name 1" }, new Position { Id = 2, Name = "name 2" }, new Position { Id = 1, Name = "also 1" } }; 
     var distinct = all.Distinct(new Comaprer<Position>()); 

     foreach(var d in distinct) 
     { 
      Console.WriteLine(d.Name); 
     } 
     Console.ReadKey(); 
    } 
} 
+0

इस नहीं है पहले से ही उत्तर? – nawfal

+0

हाँ मैं संकलित और इस कोड को –

8

अद्यतन: काम कर रहे उदाहरण bellow यह सवाल the subject of my blog in July 2013 था। महान सवाल के लिए धन्यवाद!


आपने जेनेरिक विधि प्रकार अनुमान एल्गोरिदम में एक दुर्भाग्यपूर्ण एज केस खोजा है। हम:

Distinct<X>(IEnumerable<X>, IEqualityComparer<X>) 

जहां इंटरफेस हैं:

IEnumerable<out T> -- covariant 

और

IEqualityComparer<in T> -- contravariant 

जब हम allPositions से IEnumerable<X> को अनुमान कर हम कहते हैं कि "IEnumerable<T> टी में covariant है, इसलिए हम Positionया अन्य कोई बड़ी प्रकार स्वीकार कर सकते हैं। (ए आधार प्रकार विज्ञापन की तुलना में "बड़ा" है मिटाया प्रकार; वहाँ दुनिया में जिराफ की तुलना में अधिक जानवर हैं।)

हम comparer से निष्कर्ष हम कह कर जब "IEqualityComparer<T> टी में contravariant है, इसलिए हम BaseClassस्वीकार कर सकते हैं या किसी छोटे प्रकार।"

तो क्या होता है जब यह समय वास्तव में प्रकार तर्क अनुमान की बात आती है? Position और BaseClass: हम दो उम्मीदवारों की है। दोनों ने कहा सीमा पूरा करते हैं। Position संतुष्ट पहले बाध्य यह पहली बाध्य के समान है, और दूसरा बाध्य को संतुष्ट करता है, क्योंकि यह दूसरा बाध्य तुलना में छोटा है। BaseClass पहली बाध्यता को पूरा करता है क्योंकि यह पहली बाध्य से बड़ा है, और दूसरी बाध्य के समान है।

हम दो विजेताओं की है। हमें टाई ब्रेकर की जरूरत है। हम इस स्थिति में क्या करते हैं?

यह कुछ बहस का एक मुद्दा था और तीन तरफ से बहस कर रहे हैं:, प्रकार के और अधिक विशिष्ट चयन प्रकार के अधिक सामान्य चुनते हैं, या प्रकार निष्कर्ष असफल है। मैं पूरे तर्क को दोबारा नहीं दूँगा लेकिन कहने के लिए पर्याप्त है कि "अधिक सामान्य चुनें" पक्ष दिन जीता।

(मामलों को और भी खराब बनाना, कल्पना में एक टाइपो है जो कहता है कि "अधिक विशिष्ट चुनें" करना सही बात है! यह डिजाइन प्रक्रिया के दौरान एक संपादन त्रुटि का परिणाम था जिसे कभी भी सही नहीं किया गया था। कंपाइलर लागू करता है "अधिक सामान्य चुनें"। मैंने त्रुटि के मैड्स को याद दिलाया है और उम्मीद है कि यह सी # 5 स्पेक में तय हो जाएगा।)

तो वहां आप जाते हैं। इस स्थिति में, प्रकार का अनुमान अधिक सामान्य प्रकार चुनता है और अनुमान लगाता है कि कॉल का अर्थ Distinct<BaseClass> है। टाइप अनुमान वापसी प्रकार खाते में कभी भी नहीं लेता है, और यह निश्चित रूप से नहीं लेता है कि अभिव्यक्ति को पर खाते में क्या आवंटित किया जा रहा है, इसलिए तथ्य यह है कि यह एक प्रकार का चयन करता है जो असाइन-टू वैरिएबल के साथ असंगत है, यह नहीं है व्यापार।

मेरी सलाह को स्पष्ट रूप से इस मामले में प्रकार तर्क राज्य के लिए है।

+0

(IEqualityComparer , टी IEnumerable ,) चलाने 'टी MyMethod में' 'Giraffe' और' Animal' के बीच एक टाई के साथ, और अधिक सामान्य प्रकार 'Animal' चुनने: आप असाइन नहीं कर सकते परिणामस्वरूप 'जिराफ' चर है, लेकिन यह 'डक' को तीसरे तर्क के रूप में अनुमति देता है। हालांकि, अधिक विशिष्ट प्रकार 'जिराफ' चुनना: यदि आवश्यक हो तो वापसी मूल्य को बड़े पैमाने पर 'पशु' में डाला जा सकता है; और 'आईन्यूमेरेबल ' का रिटर्न प्रकार 'आईएनयूमेरेबल ' पर निर्भर करता है। और यह सबसे अधिक इस्तेमाल किया संस्करण इंटरफ़ेस है। मुझे आश्चर्य है कि अधिक सामान्य प्रकार क्यों चुना गया था? – Virtlink

+0

चौथी पसंद के बारे में क्या: संकलित करने वाला एक चुनें? – Tergiver

+1

@ टर्गीवर: फिर हम जो कर रहे हैं वह * संदर्भ * को ध्यान में ले रहा है, जो एनपी-हार्ड समस्याओं को हल करने के लिए निष्पक्ष रूप से नेतृत्व करता है जिसे हम हल नहीं करेंगे। यह सब बहुत आसान है जब संदर्भ निर्दिष्ट प्रकार के चर के लिए एक असाइनमेंट है, लेकिन क्या होगा यदि चर 'var' है? क्या होगा यदि संदर्भ स्वयं एक विधि के लिए तर्क कॉल में है? अब हमें यह समझने के लिए * उस * विधि पर ओवरलोड रिज़ॉल्यूशन की समस्याएं करनी होंगी कि यह पता लगाने के लिए कि कौन सा संकलन करता है। –

संबंधित मुद्दे

 संबंधित मुद्दे