2013-08-01 8 views
16

मैं सी # में एक सॉफ्टवेयर बना रहा हूं। मैं एक अमूर्त वर्ग, Instruction, कोड की इन बिट्स है कि उपयोग कर रहा हूँ:'कन्स्ट्रक्टर में वर्चुअल विधि कॉल' समस्या को हल करना

protected Instruction(InstructionSet instructionSet, ExpressionElement newArgument, 
    bool newDoesUseArgument, int newDefaultArgument, int newCostInBytes, bool newDoesUseRealInstruction) { 

    //Some stuff 

    if (DoesUseRealInstruction) { 
     //The warning appears here. 
     RealInstruction = GetRealInstruction(instructionSet, Argument); 
    } 
} 

और

public virtual Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) { 
    throw new NotImplementedException("Real instruction not implemented. Instruction type: " + GetType()); 
} 

तो Resharper मुझसे कहता है कि चिह्नित लाइन पर मैं निर्माता में एक आभासी विधि बुला 'कर रहा हूँ और यह बुरा है। मैं उस क्रम के बारे में बात समझता हूं जिसमें रचनाकारों को बुलाया जाता है। GetRealInstruction विधि के सभी ओवरराइड इस तरह दिखेगा:

public override Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) { 
    return new GoInstruction(instructionSet, argument); 
} 

तो वे कक्षा में किसी भी डेटा पर निर्भर नहीं है; वे सिर्फ कुछ लौटाते हैं जो व्युत्पन्न प्रकार पर निर्भर करता है। (इसलिए कन्स्ट्रक्टर ऑर्डर उन्हें प्रभावित नहीं करता है)।

तो, क्या मुझे इसे अनदेखा करना चाहिए? मै शायद नहीं; तो क्या कोई मुझे दिखा सकता है कि मैं इस चेतावनी से कैसे बच सकता हूं?

मैं प्रतिनिधियों का अच्छी तरह से उपयोग नहीं कर सकता क्योंकि GetRealInstruction विधि में एक और अधिभार है।

उत्तर

16

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

public abstract class Instruction 
{ 
    protected Instruction(InstructionSet instructionSet, ExpressionElement argument, RealInstructionGetter realInstructionGetter) 
    { 
     if (realInstructionGetter != null) 
     { 
      RealInstruction = realInstructionGetter.GetRealInstruction(instructionSet, argument); 
     } 
    } 

    public Instruction RealInstruction { get; set; } 

    // Abstracted what used to be the virtual method, into it's own class that itself can be inherited from. 
    // When doing this I often make them inner/nested classes as they're not usually relevant to any other classes. 
    // There's nothing stopping you from making this a standalone class of it's own though. 
    protected abstract class RealInstructionGetter 
    { 
     public abstract Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument); 
    } 
} 

// A sample derived Instruction class 
public class FooInstruction : Instruction 
{ 
    // Passes a concrete instance of a RealInstructorGetter class 
    public FooInstruction(InstructionSet instructionSet, ExpressionElement argument) 
     : base(instructionSet, argument, new FooInstructionGetter()) 
    { 
    } 

    // Inherits from the nested base class we created above. 
    private class FooInstructionGetter : RealInstructionGetter 
    { 
     public override Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) 
     { 
      // Returns a specific real instruction 
      return new FooRealInstuction(instructionSet, argument); 
     } 
    } 
} 

// Another sample derived Instruction classs showing how you effictively "override" the RealInstruction that is passed to the base class. 
public class BarInstruction : Instruction 
{ 
    public BarInstruction(InstructionSet instructionSet, ExpressionElement argument) 
     : base(instructionSet, argument, new BarInstructionGetter()) 
    { 
    } 

    private class BarInstructionGetter : RealInstructionGetter 
    { 
     public override Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) 
     { 
      // We return a different real instruction this time. 
      return new BarRealInstuction(instructionSet, argument); 
     } 
    } 
} 

अपने विशिष्ट उदाहरण में यह एक छोटे से भ्रामक प्राप्त करता है और मैं समझदार नामों में से बाहर चलाने के लिए शुरू कर दिया है, लेकिन इस वजह से है इस तथ्य को आप पहले से ही निर्देश के भीतर निर्देश के एक घोंसले है, यानी एक निर्देश एक RealInstruction है (या कम से कम वैकल्पिक रूप से करता है); लेकिन जैसा कि आप देख सकते हैं कि आप जो चाहते हैं उसे प्राप्त करना अभी भी संभव है और किसी कन्स्ट्रक्टर से किसी आभासी सदस्य कॉल से बचें।

मामला अभी भी स्पष्ट नहीं है कि में, मैं भी एक मैं अभी हाल ही में अपने खुद के कोड में इस्तेमाल के आधार पर एक उदाहरण देता हूँ। इस मामले में मेरे पास 2 प्रकार के रूप हैं, एक हेडर फॉर्म और एक संदेश फ़ॉर्म है, जिनमें से दोनों मूल रूप से प्राप्त होते हैं। सभी रूपों क्षेत्रों है, लेकिन प्रत्येक प्रपत्र प्रकार के क्षेत्रों के निर्माण के लिए एक अलग तंत्र है, तो मैं मूल रूप से एक अमूर्त विधि कहा जाता GetOrderedFields कि मैं आधार निर्माता से फोन किया और विधि प्रत्येक व्युत्पन्न प्रपत्र वर्ग में ओवरराइड किया गया था। इससे मुझे आपके द्वारा उल्लिखित रिशेर्पर चेतावनी दी गई। मेरे समाधान ऊपर के रूप में एक ही पैटर्न था और के रूप में

internal abstract class FormInfo 
{ 
    private readonly TmwFormFieldInfo[] _orderedFields; 

    protected FormInfo(OrderedFieldReader fieldReader) 
    { 
     _orderedFields = fieldReader.GetOrderedFields(formType); 
    } 

    protected abstract class OrderedFieldReader 
    { 
     public abstract TmwFormFieldInfo[] GetOrderedFields(Type formType); 
    } 
} 

internal sealed class HeaderFormInfo : FormInfo 
{ 
    public HeaderFormInfo() 
     : base(new OrderedHeaderFieldReader()) 
    { 
    } 

    private sealed class OrderedHeaderFieldReader : OrderedFieldReader 
    { 
     public override TmwFormFieldInfo[] GetOrderedFields(Type formType) 
     { 
      // Return the header fields 
     } 
    } 
} 

internal class MessageFormInfo : FormInfo 
{ 
    public MessageFormInfo() 
     : base(new OrderedMessageFieldReader()) 
    { 
    } 

    private sealed class OrderedMessageFieldReader : OrderedFieldReader 
    { 
     public override TmwFormFieldInfo[] GetOrderedFields(Type formType) 
     { 
      // Return the message fields 
     } 
    } 
} 
+0

मैं उल्लेख करना भूल गया था, लेकिन इस तरह का एक अन्य लाभ यह है कि आपके मामले में आप मूल विधि के बजाय आधार विधि वर्चुअल होने की आवश्यकता से बचते हैं, इसलिए आपको अपवाद फेंकने के बजाय संकलन-समय की जांच मिलती है (जैसा कि @TarasDzyoba उल्लेख किया गया है) । – Richard

+0

यह बहुत अच्छा विचार है, धन्यवाद। अंत में मैंने इस समस्या से एक और तरीके से परहेज किया, लेकिन यह भविष्य में एक बहुत अच्छा समाधान है। –

1

आप आधार वर्ग निर्माता में वास्तविक अनुदेश पास कर सकता है:

protected Instruction(..., Instruction realInstruction) 
{ 
    //Some stuff 

    if (DoesUseRealInstruction) { 
     RealInstruction = realInstruction; 
    } 
} 

public DerivedInstruction(...) 
    : base(..., GetRealInstruction(...)) 
{ 
} 

या, आप वास्तव में अपने निर्माता से एक आभासी समारोह (जो मैं अत्यधिक से आप discorage) आप को दबाने सकता है कॉल करना चाहते हैं, तो ReSharper चेतावनी:

// ReSharper disable DoNotCallOverridableMethodsInConstructor 
    RealInstruction = GetRealInstruction(instructionSet, Argument); 
// ReSharper restore DoNotCallOverridableMethodsInConstructor 
+0

कि ठीक काम करेगा, लेकिन यह चेतावनी के कारण नहीं निकलेगी, बस इसे छिपाना। यदि कोई अन्य समाधान नहीं है तो मैं इसका उपयोग कर सकता हूं। मुझे एक बार काम करना पसंद है और खुद को स्थापित करने के लिए चीजों को सेट करना पसंद है। मुझे कॉलर को सेटअप देना पसंद नहीं है। –

+0

उत्तर संशोधित। लेकिन मुझे लगता है कि यह अभी भी वह नहीं है जिसे आप ढूंढ रहे हैं। और मुझे संदेह है कि ऐसा समाधान है। ReSharper चेतावनी के लिए [एक अच्छा कारण] (http://stackoverflow.com/questions/119506/virtual-member-call-in-a-constructor?rq=1) है। –

+0

मैं समझता हूं। मैंने 'GetRealInstruction' कॉल को किसी अन्य फ़ंक्शन पर ले जाना समाप्त कर दिया, ताकि इसे जरूरी होने पर ही बुलाया जा सके। –

2

जब आप अपने व्युत्पन्न वर्ग का एक उदाहरण बनाते हैं तो आपके कॉल स्टैक इस तरह दिखेगा:

GetRealInstruction() 
BaseContructor() 
DerivedConstructor() 

GetRealInstruction व्युत्पन्न कक्षा में ओवरराइड है, जिसका निर्माता अभी तक समाप्त नहीं हुआ है।

मुझे नहीं पता कि आपका दूसरा कोड कैसा दिखता है, लेकिन आपको पहले यह जांचना चाहिए कि आपको वास्तव में इस मामले में सदस्य चर की आवश्यकता है या नहीं। आपके पास एक ऐसी विधि है जो आपको आवश्यक ऑब्जेक्ट देता है। यदि आपको वास्तव में इसकी आवश्यकता है, तो संपत्ति बनाएं और गेटटर में GetRealInstruction() पर कॉल करें।

इसके अलावा आप GetRealInstruction सार बना सकते हैं। इस तरह आपको अपवाद फेंकने की ज़रूरत नहीं है और संकलक आपको एक त्रुटि देगा यदि आप इसे व्युत्पन्न कक्षा में ओवरराइड करना भूल जाते हैं।

+0

मैं कॉलिंग ऑर्डर समस्या को समझता हूं। मुझे केवल कुछ व्युत्पन्न कक्षाओं (GetRequireRealInstruction बिट सेट वाले वाले) में GetRealInstruction की आवश्यकता है, इसलिए मुझे इसे सार नहीं बनाना चाहिए। मैं वास्तव में इसके लिए कोई संपत्ति नहीं बनाना चाहता क्योंकि मुझे फ़ील्ड और विधियों के बीच पहुंच/संचालन अंतर पसंद है। (मैं इस तरह के मामलों में गुणों का उपयोग करता हूं, लेकिन बहुत सरल संचालन के लिए) मैंने कहीं और कॉल को स्थानांतरित कर दिया। –

2

तो अपने कोड की तरह दिखाई देगा आप एक और अमूर्त वर्ग RealInstructionBase लागू कर सकते हैं:

public abstract class Instruction { 
    public Instruction() { 
     // do common stuff 
    } 
} 

public abstract class RealInstructionBase : Instruction { 
    public RealInstructionBase() : base() { 
     GetRealInstruction(); 
    } 

    protected abstract object GetRealInstruction(); 
} 

अब प्रत्येक अनुदेश जो RealInstruction RealInstructionBase से निकला है और अन्य सभी अनुदेश से निकाले जाते हैं उपयोग करने के लिए की जरूरत है। इस तरह आप उन्हें सभी सही ढंग से शुरू किया जाना चाहिए।

संपादित करें: ठीक है, यह आपको केवल क्लीनर डिज़ाइन देगा (कन्स्ट्रक्टर में नहीं) लेकिन चेतावनी से छुटकारा नहीं मिलता है। अब अगर आप जानना चाहते हैं कि आपको पहली जगह चेतावनी क्यों मिलती है, तो आप this question देख सकते हैं। असल में यह मुद्दा यह है कि जब आप अपनी कक्षाओं को चिह्नित करते हैं तो आप सुरक्षित रहेंगे जहां आप अमूर्त विधि को सील कर रहे हैं।

+0

यह स्वीकार्य रूप से क्लीनर है। हालांकि, मैं इसे 'गेटरियल इंस्ट्रक्शन' कॉल को कंट्रोलर में नहीं होने से पहले इसे हल करने के लिए उलझा हुआ हूं। यह कार्य बहुत महंगा नहीं है। –

0

एक अन्य विकल्प एक Initialize() विधि है, जहां आप सभी आरंभीकरण कि एक पूरी तरह से निर्माण किया वस्तु की आवश्यकता कर लागू करने के लिए है इस प्रकार है।

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