2009-04-15 16 views
9

सी # आभासी और ओवरराइड तंत्र आंतरिक रूप से काम करता है, प्रोग्रामर के बीच मौत पर चर्चा की गई है ... लेकिन Google पर आधे घंटे के बाद, मुझे निम्नलिखित प्रश्न का उत्तर नहीं मिल रहा है (नीचे देखें):सी # वर्चुअल और ओवरराइड के आंतरिक कार्य

एक सरल कोड का उपयोग करना: बिना

public class BaseClass 
{ 
    public virtual SayNo() { return "NO!!!"; } 
} 

public class SecondClass: BaseClass 
{ 
    public override SayNo() { return "No."; } 
} 

public class ThirdClass: SecondClass 
{ 
    public override SayNo() { return "No..."; } 
} 

class Program 
{ 
    static void Main() 
    { 
    ThirdClass thirdclass = new ThirdClass(); 
    string a = thirdclass.SayNo(); // this would return "No..." 

    // Question: 
    // Is there a way, not using the "new" keyword and/or the "hide" 
    // mechansim (i.e. not modifying the 3 classes above), can we somehow return 
    // a string from the SecondClass or even the BaseClass only using the 
    // variable "third"? 

    // I know the lines below won't get me to "NO!!!" 
    BaseClass bc = (BaseClass)thirdclass; 
    string b = bc.SayNo(); // this gives me "No..." but how to I get to "NO!!!"? 
    } 
} 

मुझे लगता है मैं आधार वर्ग या मध्यवर्ती व्युत्पन्न वर्ग केवल सबसे व्युत्पन्न उदाहरण का उपयोग करने के तरीकों को नहीं मिल सकता है (3 कक्षाओं के विधि हस्ताक्षर संशोधित)। लेकिन मैं अपनी समझ की पुष्टि करना और सीमेंट करना चाहता हूं ...

धन्यवाद।

+0

आप अपने वर्गों में से एक के लिए इरादा था BasClass से प्राप्त करना (SecondClass कहते हैं)? –

+0

कोई नहीं; जोड़ने या बदलने के लिए और कक्षाएं नहीं ... – henry000

उत्तर

1

आप ओवरराइड के मूल तरीकों तक नहीं पहुंच सकते हैं। इससे कोई फर्क नहीं पड़ता कि आपने वस्तु को कैसे डाला है, उदाहरण में अंतिम ओवरराइड हमेशा उपयोग किया जाता है।

+0

यह पूरी तरह से सच नहीं है। आप बेस क्लास में एक ओवरराइड विधि को कॉल करने के लिए जेरेड्स के जवाब में हमेशा "आधार" का उपयोग कर सकते हैं, इस मामले में SecondClass। – Pete

+1

नहीं यदि आप ऑब्जेक्ट के बाहर हैं तो आप नहीं कर सकते हैं और यही सवाल लेखक ने पूछा है। बेशक आप कॉलवर्ट की बजाय आईएल कॉल का उपयोग करके इसे प्राप्त कर सकते हैं, लेकिन सी # विशेष है कि यह स्थैतिक तरीकों को छोड़कर कॉल को कभी भी उत्सर्जित नहीं करता है। – grover

7

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

हालांकि इसके आसपास आप कुछ तरीकों से काम कर सकते हैं।

विकल्प 1: आप ThirdClass को

public void SayNoBase() { 
    base.SayNo(); 
} 

निम्न विधि जोड़ सकता है यह SecondClass.SayNo

के आह्वान

विकल्प 2 के लिए मजबूर होगा: मुख्य समस्या यह है कि यहाँ आप एक आभासी आह्वान करना चाहते हैं विधि गैर-वर्चुअल रूप से। सी # केवल आधार संशोधक के माध्यम से ऐसा करने का एक तरीका प्रदान करता है। यह गैर-आभासी फैशन में अपनी कक्षा के भीतर एक विधि को कॉल करना असंभव बनाता है। आप इसे दूसरी विधि और प्रॉक्सिंग में फैक्टर करके इसे ठीक कर सकते हैं।

public overrides void SayNo() { 
    SayNoHelper(); 
} 

public void SayNoHelper() { 
    Console.WriteLine("No"); 
} 
+0

इसके अलावा, यदि आपके पास सार्वजनिक वर्ग थर्ड क्लास: बेसक्लास { आधार.SayNo(); } वह वापस नहीं आएगा !!! – Pete

2

ज़रूर ...

BaseClass bc = new BaseClass(); 
    string b = bc.SayNo(); 

"आभासी" मतलब है कि कार्यान्वयन जो अंतर्निहित वस्तु के वास्तविक प्रकार के आधार पर किया जाता है निष्पादित किया जाएगा, नहीं चर के प्रकार यह है भरवां ... तो यदि वास्तविक वस्तु एक थर्ड क्लास है, तो वह कार्यान्वयन है जिसे आप प्राप्त करेंगे, इससे कोई फर्क नहीं पड़ता कि आपने इसे क्या किया है। यदि आप ऊपर वर्णित व्यवहार चाहते हैं, तो विधियों को वर्चुअल नहीं बनाएं ...

यदि आप सोच रहे हैं "बिंदु क्या है?" यह 'बहुरूपता' के लिए है; ताकि आप कुछ आधार प्रकार के रूप में संग्रह, या विधि पैरामीटर घोषित कर सकें, और इसमें व्युत्पन्न प्रकारों का मिश्रण शामिल/पास कर सकें, और फिर भी, कोड के भीतर, भले ही प्रत्येक ऑब्जेक्ट को एक रेफ़र वैरिएबल को असाइन किया गया हो आधार प्रकार, प्रत्येक के लिए, वास्तविक कार्यान्वयन जिसे किसी भी वर्चुअल विधि कॉल के लिए निष्पादित किया जाएगा, वह क्रिया होगी जो प्रत्येक ऑब्जेक्ट के वास्तविक टाई के लिए कक्षा परिभाषा में परिभाषित किया गया है ...

0

यदि यह किसी फ़ील्ड के साथ समर्थित है तो आप प्रतिबिंब का उपयोग कर मैदान खींचें।

भले ही आप MethodInfo typeof (BaseClass) से प्रतिबिंब का उपयोग कर आप अभी भी अपने ओवरराइड विधि को क्रियान्वित करने

14

सी # ऐसा नहीं कर सकते खत्म हो जाएगा हटा, लेकिन यह वास्तव में के बजाय call का उपयोग कर आईएल में संभव है callvirtDynamicMethod के साथ संयोजन में आप Reflection.Emit का उपयोग कर सी # की सीमा के आसपास काम कर सकते हैं।

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

delegate string SayNoDelegate(BaseClass instance); 

static void Main() { 
    BaseClass target = new SecondClass(); 

    var method_args = new Type[] { typeof(BaseClass) }; 
    var pull = new DynamicMethod("pull", typeof(string), method_args); 
    var method = typeof(BaseClass).GetMethod("SayNo", new Type[] {}); 
    var ilgen = pull.GetILGenerator(); 
    ilgen.Emit(OpCodes.Ldarg_0); 
    ilgen.EmitCall(OpCodes.Call, method, null); 
    ilgen.Emit(OpCodes.Ret); 

    var call = (SayNoDelegate)pull.CreateDelegate(typeof(SayNoDelegate)); 
    Console.WriteLine("callvirt, in C#: {0}", target.SayNo()); 
    Console.WriteLine("call, in IL: {0}", call(target)); 
} 

प्रिंटों:

callvirt, in C#: No. 
call, in IL: NO!!! 
+1

मैंने सी # के माध्यम से केवल सीएलआर के पहले कुछ पेज पढ़े हैं, लेकिन इस तरह के उत्तर मुझे दिन बंद करना और इसे खत्म करना चाहते हैं! – overslacked

+0

@ ओवरस्लेक्ड, मैं भी। मैं पुस्तक को समाप्त करने के लिए पर्याप्त समय चाहता हूं: सी # के माध्यम से सीएलआर। – Attilah

2

सी # में base का उपयोग केवल तत्काल आधार के लिए काम करता है। आप बेस-बेस सदस्य तक नहीं पहुंच सकते हैं।

ऐसा लगता है कि आईएल में ऐसा करने के बारे में उत्तर देने के साथ किसी और ने मुझे पंच को मार दिया।

हालांकि, मुझे लगता है कि जिस तरह से मैंने कोड जेन किया है उसके कुछ फायदे हैं, इसलिए मैं इसे किसी भी तरह पोस्ट करूंगा।

मैंने जो चीज अलग-अलग की थी वह अभिव्यक्ति पेड़ों का उपयोग करना है, जो आपको सी # कंपाइलर को ओवरलोड रिज़ॉल्यूशन और जेनेरिक तर्क प्रतिस्थापन करने के लिए उपयोग करने में सक्षम बनाता है।

वह सामान जटिल है, और यदि आप इसकी सहायता कर सकते हैं तो आप इसे स्वयं को दोहराना नहीं चाहते हैं। आपके मामले में, कोड इस तरह काम करेगा:

var del = 
    CreateNonVirtualCall<Program, BaseClass, Action<ThirdClass>> 
    (
     x=>x.SayNo() 
    ); 

आप शायद एक केवल पढ़ने के लिए स्थिर क्षेत्र में प्रतिनिधि स्टोर करने के लिए, ताकि आप केवल एक बार यह संकलन करने के लिए है चाहेंगे। यह वर्ग है कि आप कोड लागू है | आप "CreateNonVirtualCall" का उपयोग नहीं कर रहे थे, तो से है -

  1. मालिक के प्रकार:

    आप 3 सामान्य तर्क निर्दिष्ट करने के लिए की जरूरत है।

  2. आधार वर्ग - इस वर्ग आप से

  3. एक प्रतिनिधि प्रकार गैर आभासी कॉल करनी है। यह "इस" तर्क के लिए अतिरिक्त पैरामीटर के साथ बुलाए जाने वाले विधि के हस्ताक्षर का प्रतिनिधित्व करना चाहिए। इसे खत्म करना संभव है, लेकिन कोड कोड विधि में इसे और अधिक काम की आवश्यकता है।

विधि एक एकल तर्क लेता है, एक लैम्ब्डा कॉल का प्रतिनिधित्व करता है। इसे एक कॉल होना चाहिए, और केवल एक कॉल होना है। यदि आप कोड जेन का विस्तार करना चाहते हैं तो आप अधिक जटिल सामग्री का समर्थन कर सकते हैं।

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

यह ध्यान रखना महत्वपूर्ण है कि यह कोड सही नहीं है। यह बहुत अधिक सत्यापन का उपयोग कर सकता है, और यह अभिव्यक्ति वृक्ष सीमाओं के कारण "रेफरी" या "आउट" पैरामीटर के साथ काम नहीं करता है।

मैंने नमूना मामलों में शून्य विधियों, मूल्यों को वापस करने के तरीकों और सामान्य तरीकों के साथ परीक्षण किया, और यह काम किया। मुझे यकीन है कि, हालांकि, आप कुछ किनारे के मामलों को पा सकते हैं जो काम नहीं करते हैं।

किसी भी मामले में, यहाँ आईएल जनरल कोड है:

public static TDelegate CreateNonVirtCall<TOwner, TBase, TDelegate>(Expression<TDelegate> call) where TDelegate : class 
{ 
    if (! typeof(Delegate).IsAssignableFrom(typeof(TDelegate))) 
    { 
     throw new InvalidOperationException("TDelegate must be a delegate type."); 
    } 

    var body = call.Body as MethodCallExpression; 

    if (body.NodeType != ExpressionType.Call || body == null) 
    { 
     throw new ArgumentException("Expected a call expression", "call"); 
    } 

    foreach (var arg in body.Arguments) 
    { 
     if (arg.NodeType != ExpressionType.Parameter) 
     { 
      //to support non lambda parameter arguments, you need to add support for compiling all expression types. 
      throw new ArgumentException("Expected a constant or parameter argument", "call"); 
     } 
    } 

    if (body.Object != null && body.Object.NodeType != ExpressionType.Parameter) 
    { 
     //to support a non constant base, you have to implement support for compiling all expression types. 
     throw new ArgumentException("Expected a constant base expression", "call"); 
    } 

    var paramMap = new Dictionary<string, int>(); 
    int index = 0; 

    foreach (var item in call.Parameters) 
    { 
     paramMap.Add(item.Name, index++); 
    } 

    Type[] parameterTypes; 


    parameterTypes = call.Parameters.Select(p => p.Type).ToArray(); 

    var m = 
     new DynamicMethod 
     (
      "$something_unique", 
      body.Type, 
      parameterTypes, 
      typeof(TOwner) 
     ); 

    var builder = m.GetILGenerator(); 
    var callTarget = body.Method; 

    if (body.Object != null) 
    { 
     var paramIndex = paramMap[((ParameterExpression)body.Object).Name]; 
     builder.Emit(OpCodes.Ldarg, paramIndex); 
    } 

    foreach (var item in body.Arguments) 
    { 
     var param = (ParameterExpression)item; 

     builder.Emit(OpCodes.Ldarg, paramMap[param.Name]); 
    } 

    builder.EmitCall(OpCodes.Call, FindBaseMethod(typeof(TBase), callTarget), null); 

    if (body.Type != typeof(void)) 
    { 
     builder.Emit(OpCodes.Ret); 
    } 

    var obj = (object) m.CreateDelegate(typeof (TDelegate)); 
    return obj as TDelegate; 
} 
+1

काम का कूल टुकड़ा। निश्चित रूप से इसे पोस्ट करने लायक है! –

+0

धन्यवाद। (argh! न्यूनतम टिप्पणी लंबाई) –

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