2009-05-28 16 views
6

की जेनिक्स, विरासत, और असफल विधि संकल्प आज मैं एक संकलन मुद्दे में भाग गया जो मुझे परेशान करता था। इन दो कंटेनर वर्गों पर विचार करें।सी # कंपाइलर

public class BaseContainer<T> : IEnumerable<T> 
{ 
    public void DoStuff(T item) { throw new NotImplementedException(); } 

    public IEnumerator<T> GetEnumerator() { } 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { } 
} 
public class Container<T> : BaseContainer<T> 
{ 
    public void DoStuff(IEnumerable<T> collection) { } 

    public void DoStuff <Tother>(IEnumerable<Tother> collection) 
     where Tother: T 
    { 
    } 
} 

पूर्व परिभाषित करता है DoStuff(T item) और DoStuff <Tother>(IEnumerable<Tother>) विशेष रूप से के साथ बाद भार के यह सी # के covariance/contravariance के अभाव के आसपास पाने के लिए (4 जब तक मैंने सुना है)।

इस कोड

Container<string> c = new Container<string>(); 
c.DoStuff("Hello World"); 

एक नहीं बल्कि अजीब संकलन त्रुटि पूरी करता है। विधि कॉल से <char> की अनुपस्थिति पर ध्यान दें।

प्रकार 'चार' सामान्य प्रकार या विधि 'Container.DoStuff (System.Collections.Generic.IEnumerable)' के रूप में प्रकार पैरामीटर 'Tother' नहीं किया जा सकता। 'Char' से 'string' तक कोई मुक्केबाजी रूपांतरण नहीं है।

अनिवार्य रूप से, संकलक Container.DoStuff<char>(IEnumerable<char>) क्योंकि string औजार IEnumerable<char> में DoStuff(string) करने के लिए अपने कॉल जाम करने के बजाय BaseContainer.DoStuff(string) का उपयोग कर रहा है।

एक ही रास्ता है कि मैं इस संकलन बनाने के लिए मिल गया है व्युत्पन्न वर्ग के लिए DoStuff(T) जोड़ना है

public class Container<T> : BaseContainer<T> 
{ 
    public new void DoStuff(T item) { base.DoStuff(item); } 

    public void DoStuff(IEnumerable<T> collection) { } 

    public void DoStuff <Tother>(IEnumerable<Tother> collection) 
     where Tother: T 
    { 
    } 
} 

क्यों संकलक IEnumerable<char> के रूप में एक स्ट्रिंग जाम करने जब 1) यह जानता है कि यह कर सकते हैं 'कोशिश कर रहा है टी (संकलन त्रुटि की उपस्थिति को देखते हुए) और 2) इसमें बेस क्लास में एक विधि है जो ठीक से संकलित करती है? क्या मैं सी # में जेनेरिक या वर्चुअल विधि सामग्री के बारे में कुछ गलत समझ रहा हूं? क्या new DoStuff(T item) से Container जोड़ने के अलावा कोई अन्य फ़िक्स है?

+3

का उपयोग कर मैं मानता हूँ यह अजीब लगता है, लेकिन यह कल्पना के अनुसार सही है। यह दो नियमों की बातचीत का एक परिणाम है: (1) ओवरलोड रिज़ॉल्यूशन प्रयोज्यता जांच बाध्यता जांच से पहले होती है, और (2) व्युत्पन्न कक्षाओं में लागू विधियां बेस कक्षाओं में लागू विधियों से हमेशा बेहतर होती हैं। दोनों उचित समझदार नियम हैं; वे सिर्फ आपके मामले में विशेष रूप से बुरी तरह से बातचीत करते हैं। –

+1

विवरण के लिए खंड 7.5.5.1 देखें, विशेष रूप से बिट्स जो कहते हैं: (1) "यदि सबसे अच्छी विधि एक सामान्य विधि है, तो टाइप तर्क (आपूर्ति या अनुमानित) बाधाओं के खिलाफ चेक किए जाते हैं ..." और (2) " उम्मीदवार विधियों का सेट कम से कम व्युत्पन्न प्रकारों से केवल विधियों को कम करने के लिए कम किया गया है ... " –

+2

अंततः आपकी समस्या एक डिज़ाइन समस्या है। आप "DoStuff" विधि को ओवरलोड कर रहे हैं, जिसका अर्थ है "टाइप टी के एक ही मान पर सामान करें" और "टाइप टी के मानों के अनुक्रम में सामान करें"। यह कई तरीकों से गंभीर "इरादा संकल्प" समस्याओं में चलता है - उदाहरण के लिए, जब "टाइप टी" स्वयं एक अनुक्रम होता है। आप पाएंगे कि बीसीएल में मौजूदा संग्रह कक्षाओं को इस समस्या से बचने के लिए सावधानीपूर्वक डिजाइन किया गया है; किसी आइटम को लेने वाली विधियों को "फ्रोब" कहा जाता है, वस्तुओं के अनुक्रम को लेने वाले विधियों को "FrobRange" कहा जाता है, उदाहरण के लिए सूचियों पर "जोड़ें" और "AddRange" कहा जाता है। –

उत्तर

3

संपादित

ठीक है ... मुझे लगता है कि मैं अब अपने भ्रम की स्थिति को देखते हैं। आप उम्मीद करते थे कि DoStuff (स्ट्रिंग) को पैरामीटर को एक स्ट्रिंग के रूप में रखा होगा और बेसक्लास विधि सूची को पहले उपयुक्त हस्ताक्षर की तलाश में चला गया था, और किसी अन्य प्रकार के पैरामीटर को डालने का प्रयास करने के लिए उस फ़ॉलबैक को विफल कर दिया था।

लेकिन यह दूसरी तरफ हुआ ... इसके बजाय Container.DoStuff(string) चला गया, "वहां एक बेस क्लास विधि है जो कि बिल को फिट करती है, लेकिन मैं एक आईनेमरेबल में परिवर्तित करने जा रहा हूं और इसमें क्या उपलब्ध है इसके बारे में दिल का दौरा पड़ता है बजाय वर्तमान वर्ग ...

हममम ... मैं कर रहा हूँ यकीन है कि जॉन या मार्क इस विशेष कोने मामले

मूल

कवर विशिष्ट सी # युक्ति पैराग्राफ के साथ इस बिंदु पर झंकार करने में सक्षम होगा

दोनों तरीके एक आईनेमरेबल कर्नल की अपेक्षा करते हैं लीक्शन

आप एक व्यक्तिगत स्ट्रिंग पास कर रहे हैं।

संकलक कि स्ट्रिंग ले रही है और जा रहा है,

ठीक है, मैं एक स्ट्रिंग है, दोनों ही तरीकों से एक IEnumerable<T> उम्मीद करते हैं, तो मैं एक IEnumerable<char> में इस स्ट्रिंग बारी होगा ... हो गया

ठीक है, पहली विधि की जाँच करें ... हममम ... इस वर्ग के एक Container<string> है, लेकिन मैं एक IEnumerable<char> तो यह सही नहीं है की है।

चेक दूसरी विधि, हममम .... मैं एक IEnumerable<char> लेकिन चार स्ट्रिंग को लागू नहीं करता है तो यह है कि नहीं सही या तो ।

कंपाइलर त्रुटि

तो क्या # रों ठीक है, अच्छी तरह यह पूरी तरह से निर्भर करता है आपके क्या प्राप्त करना चाहते ... निम्न दोनों मान्य होगा, अनिवार्य रूप से, अपने प्रकार के उपयोग में सिर्फ सही नहीं है आपका अवतार

 Container<char> c1 = new Container<char>(); 
     c1.DoStuff("Hello World"); 

     Container<string> c2 = new Container<string>(); 
     c2.DoStuff(new List<string>() { "Hello", "World" }); 
+0

तो कंपाइलर केवल पैरामीटर के प्रकारों द्वारा ओवरलोड लोड करता है और उन पर बाधाओं को नहीं रखता है (पूरे पैरामीटर + बाधाओं के रूप में नहीं)? यह अपेक्षाकृत कमजोर और आधा बेक्ड लगता है। यह * एक विधि ढूंढ सकता है जिसे * * उपयोग कर सकता है लेकिन यह * नहीं * चुन रहा है। –

+0

नहीं, यह नहीं हो सकता है ... आपके तरीकों में से कोई भी आपके पास जाने वाले प्रकार को संतुष्ट नहीं करता है। कंटेनर वर्तमान में केवल एक DoStuff (IEnumerable ) या DoStuff (IEnumerable ) निष्पादित कर सकता है <- जो स्ट्रिंग के बाद से कुछ भी नहीं है मुहरबंद वर्ग –

+0

यह नहीं कर सकता? बेसकॉन्टेनर.डोस्टफ (टी) के साथ क्या हुआ? आपके फिक्स संपादन के बारे में। मैं स्ट्रिंग का एक कंटेनर चाहता हूं और स्ट्रिंग "हैलो वर्ल्ड" को DoStuff'ed होना चाहता हूं। अगर मैं कक्षा को ठीक नहीं कर सकता तो DoStuff (नई स्ट्रिंग [] {"हैलो वर्ल्ड"}); जो मैं चाहता हूं, लेकिन यह वास्तव में एक क्रमी एपीआई प्रदान करने के लिए है (मुझे लगता है)। –

1

मुझे लगता है कि इस तथ्य के साथ कुछ करना है कि char एक मान प्रकार है और स्ट्रिंग एक संदर्भ प्रकार है। ऐसा लगता है कि आप

TOther : T 

और char स्ट्रिंग से प्राप्त नहीं होते हैं।

+0

इसलिए मेरा विवेकपूर्ण तरीका यह है कि मैं जिस विधि को आमंत्रित कर रहा हूं, वह DoStuff (IENumerable ) चुन रहा है। मैं DoStuff निर्दिष्ट नहीं कर रहा हूं स्ट्रिंग से प्राप्त चार नहीं है। यह किसी भी तरह से समझ में नहीं आता है। –

+0

मुझे आश्चर्य है कि स्ट्रिंग को IENumerable पर डाला जा सकता है और संकलक टोर को चार होने का कारण बता रहा है? – n8wrl

+0

मुझे लगता है कि यह वही है जो यह कर रहा है लेकिन मैं DoStuff , बस DoStuff का आह्वान नहीं कर रहा हूं। मैंने कभी ऐसा सामान्य नहीं माना है। –

2

कंपाइलर आईनेमेरेबल <T> पर पैरामीटर से मिलान करने का प्रयास करेगा। स्ट्रिंग प्रकार IENumerable <char> लागू करता है, इसलिए यह मानता है कि टी "char" है।

उसके बाद, कंपाइलर अन्य शर्त "जहां अन्य टी: टी" की जांच करता है, और वह स्थिति पूरी नहीं होती है। इसलिए संकलक त्रुटि।

2

मेरा GUESS, और यह एक अनुमान है क्योंकि मुझे वास्तव में पता नहीं है, यह पहली बार विधि कॉल को हल करने के लिए व्युत्पन्न कक्षा में दिखता है (क्योंकि आपका ऑब्जेक्ट व्युत्पन्न प्रकार का है)। यदि, और केवल अगर यह नहीं हो सकता है, तो यह इसे हल करने के लिए आधार वर्ग विधियों को देखने के लिए आगे बढ़ता है। आपके मामले में, क्योंकि यह

DoStuff <Tother>(IEnumerable<Tother> collection) 

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

+0

इसके अलावा यह स्पष्ट रूप से इस तथ्य के बावजूद कि यह निर्दिष्ट नहीं कर रहा है कि यह Tother = char को ग्रहण कर रहा है। मैंने कभी भी एक विधि पर जेनेरिक नहीं देखा है, बस यह माना जाता है क्योंकि यह तर्क में जाम कर सकता है। –

+1

क्या आपने कभी LINQ का उपयोग नहीं किया है? अधिकांश LINQ इस विचार पर बनाया गया है कि विधि पैरामीटर में से एक पर प्रकार प्रदान किया गया है, तो प्रकार पैरामीटर माना जा सकता है। –

+2

आप यह निर्दिष्ट कर रहे हैं कि टॉथ चार है, एक आईनेमेरेबल , उर्फ ​​"स्ट्रिंग" में गुजरकर। आप सी # की "विधि प्रकार अनुमान" सुविधा से परिचित नहीं हो सकते हैं, लेकिन यह सी # 2.0 के आसपास रहा है। यदि आप विधि प्रकार अनुमान एल्गोरिदम कैसे काम करते हैं, इस बारे में विवरण चाहते हैं तो मेरे ब्लॉग के "प्रकार अनुमान" अनुभाग देखें; वे काफी आकर्षक हैं। –

3

के रूप में एरिक Lippert बताया गया है, संकलक DoStuff<Tother>(IEnumerable<Tother>) where Tother : T {} विधि को चुनता है, क्योंकि यह की कमी की जाँच से पहले तरीकों चुनता है। चूंकि स्ट्रिंग IEnumerable<> कर सकती है, इसलिए संकलक उस बच्चे वर्ग विधि से मेल खाता है। कंपाइलर सी # विनिर्देश में वर्णित अनुसार सही ढंग से काम कर रहा है।

आपके द्वारा इच्छित विधि समाधान आदेश को DoStuff को extension method के रूप में कार्यान्वित करके मजबूर किया जा सकता है। एक्सटेंशन तरीकों के बाद आधार वर्ग तरीकों जाँच की जाती है, तो यह DoStuff के IEnumerable<Tother> के खिलाफ string मैच के लिए जब तक के बाद यह DoStuff<T> के खिलाफ यह मैच के लिए कोशिश की है की कोशिश नहीं करेंगे।

निम्नलिखित कोड वांछित विधि समाधान आदेश, covariance, और विरासत प्रदर्शित करता है। कृपया इसे एक नई परियोजना में कॉपी/पेस्ट करें।

यह सबसे बड़ी नकारात्मक पक्ष मैं की अब तक सोच सकते हैं कि आप अधिभावी तरीकों में base उपयोग नहीं कर सकते, लेकिन मुझे लगता है कि चारों ओर तरीके (पूछते हैं कि आप रुचि रखते हैं) कर रहे हैं।

BaseContainer: 
     base 
Container: 
     base 
     Container.DoStuff<Tother>() 
ContainerChild: 
     base 
     Container.DoStuff<Tother>() 
ContainerChildWithOverride: 
     base 
     ContainerChildWithOverride.DoStuff<Tother>() 
Covariance Example: 
     Container.DoStuff<Tother>() 

आपके आस-पास इस काम के साथ कोई समस्या देख सकते हैं:

using System; 
using System.Collections.Generic; 

namespace MethodResolutionExploit 
{ 
    public class BaseContainer<T> : IEnumerable<T> 
    { 
     public void DoStuff(T item) { Console.WriteLine("\tbase"); } 
     public IEnumerator<T> GetEnumerator() { return null; } 
     System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return null; } 
    }   
    public class Container<T> : BaseContainer<T> { } 
    public class ContainerChild<T> : Container<T> { } 
    public class ContainerChildWithOverride<T> : Container<T> { } 
    public static class ContainerExtension 
    { 
     public static void DoStuff<T, Tother>(this Container<T> container, IEnumerable<Tother> collection) where Tother : T 
     { 
      Console.WriteLine("\tContainer.DoStuff<Tother>()"); 
     } 
     public static void DoStuff<T, Tother>(this ContainerChildWithOverride<T> container, IEnumerable<Tother> collection) where Tother : T 
     { 
      Console.WriteLine("\tContainerChildWithOverride.DoStuff<Tother>()"); 
     } 
    } 

    class someBase { } 
    class someChild : someBase { } 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Console.WriteLine("BaseContainer:"); 
      var baseContainer = new BaseContainer<string>(); 
      baseContainer.DoStuff(""); 

      Console.WriteLine("Container:"); 
      var container = new Container<string>(); 
      container.DoStuff(""); 
      container.DoStuff(new List<string>()); 

      Console.WriteLine("ContainerChild:"); 
      var child = new ContainerChild<string>(); 
      child.DoStuff(""); 
      child.DoStuff(new List<string>()); 

      Console.WriteLine("ContainerChildWithOverride:"); 
      var childWithOverride = new ContainerChildWithOverride<string>(); 
      childWithOverride.DoStuff(""); 
      childWithOverride.DoStuff(new List<string>()); 

      //note covariance 
      Console.WriteLine("Covariance Example:"); 
      var covariantExample = new Container<someBase>(); 
      var covariantParameter = new Container<someChild>(); 
      covariantExample.DoStuff(covariantParameter); 

      // this won't work though :(
      // var covariantExample = new Container<Container<someBase>>(); 
      // var covariantParameter = new Container<Container<someChild>>(); 
      // covariantExample.DoStuff(covariantParameter); 

      Console.ReadKey(); 
     } 
    } 
} 

यहाँ उत्पादन है?

+0

हम्म, दिलचस्प विचार। मैंने बेसकॉन्टेनर पर उन ओवरलोड को नहीं रखा क्योंकि मैं उस वर्ग को बहुत बुनियादी रखने की कोशिश कर रहा हूं। मैं बेसकॉन्टेनर पर आसानी से DoStuff डाल सकता था लेकिन उस तरह बेसकॉन्टेनर को मूल रखने के अपने लक्ष्य को हरा देता है। विस्तार का मतलब है कि मैं तकनीकी रूप से बेसकॉन्टेनर को जिस तरह से चाहता हूं उसे रखता हूं लेकिन ... :) –

+0

कॉलिन, यह * बेसकॉन्टेनर पर अधिभार नहीं डालता है। यानी यह बेसकॉन्टेनर के इंटेलिजेंस को जंक नहीं करता है। कृपया इसे आज़माएं। अगर मुझे गलत समझा जाता है, तो कृपया स्पष्टीकरण दें। – dss539

+0

मेरी माफ़ी, मैं विधि प्रोटोटाइप को गलत तरीके से पढ़ता हूं। हालांकि, सामान्य वर्ग पर एक एक्सटेंशन का उपयोग करने का मतलब है कि मुझे इसे c.DoStuff के बजाय c.DoStuff द्वारा आमंत्रित करना है, है ना? –

0

मैं तुम्हें क्या हासिल करने की कोशिश कर रहे हैं पर वास्तव में स्पष्ट नहीं कर रहा हूँ, क्या आप से रोक सिर्फ दो तरीकों, DoStuff(T item) and DoStuff(IEnumerable<T> collection)?

+0

समस्या तब होती है जब टी स्वयं एक आईनेमरेबल है क्योंकि कंपाइलर DoStuff (IEnumerable ) पर नहीं है, DoStuff (T) नहीं। –

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