2011-02-05 15 views
12

मैं इस प्रश्न को सीधे जेफरी रिचटर को लिखूंगा, लेकिन पिछली बार उसने मुझे जवाब नहीं दिया :) इसलिए मैं आपकी मदद से जवाब पाने का प्रयास करूंगा :) दोस्तों :)क्या गैर-वर्चुअल विधि कॉल के आंतरिक भाग का वर्णन करते समय रिचटर गलत है?

पुस्तक में "सीएलआर के माध्यम से सी # ", 3 संस्करण, p.108 पर, जेफरी लिखते हैं:

void M3() { 
    Employee e; 
    e = new Manager(); 
    year = e.GetYearsEmployed(); 
    ... 
} 

एम 3 में कोड की अगली पंक्ति कॉल कर्मचारी का nonvirtual उदाहरण GetYearsEmployed विधि। को एक गैर-वर्चुअल इंस्टेंस विधि पर कॉल करते समय, JIT कंपाइलर टाइप ऑब्जेक्ट को रेखांकित करता है जो कॉल करने के लिए उपयोग किए जाने वाले चर के प्रकार से मेल खाता है। इस मामले में, परिवर्तनीय ई कर्मचारी के रूप में परिभाषित किया गया है। ( कर्मचारी प्रकार विधि को परिभाषित नहीं किया बुलाया जा रहा है, तो JIT कम्पाइलर चलता वस्तु की ओर वर्ग पदानुक्रम इस विधि की तलाश में नीचे। यह इस कर सकते हैं, क्योंकि प्रत्येक प्रकार वस्तु उस में एक क्षेत्र है कि इसके आधार प्रकार को संदर्भित करता है;। इस जानकारी आंकड़ों में नहीं दिखाया गया है) फिर, JIT कम्पाइलर JITs विधि स्थित प्रकार वस्तु की विधि तालिका में प्रवेश कि विधि को संदर्भित करता है बुलाया जा रहा है, (यदि आवश्यक), और फिर JITted कोड पर कॉल करें।

जब मैं यह पहली बार मैंने सोचा था कि यह वर्ग पदानुक्रम JIT-टिंग दौरान विधि की तलाश में साथ चलने के लिए प्रभावी नहीं होगा पढ़ें। संकलन चरण पर पहले से ही विधि को ढूंढना आसान है। लेकिन मैं जेफरी का मानना ​​था। मैंने इस जानकारी को किसी अन्य मंच पर पोस्ट किया और दूसरे व्यक्ति ने मेरे संदेह की पुष्टि की कि यह अजीब है और यह अप्रभावी होगा और ऐसा लगता है कि यह गलत जानकारी है।

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

public class EmployeeBase 
{ 
    public int GetYearsEmployed() { return 1; } 
} 

public class Employee : EmployeeBase 
{ 
    public void SomeOtherMethod() { } 
} 

public class Manager : Employee 
{ 
    public void GenProgressReport() { } 
} 

... 

Employee e; 
e = new Manager(); 
int years = e.GetYearsEmployed(); 

परिणामस्वरूप आईएल है:

L_0000: nop 
L_0001: newobj instance void TestProj.Form1/Manager::.ctor() 
L_0006: stloc.0 
L_0007: ldloc.0 
L_0008: callvirt instance int32 TestProj.Form1/EmployeeBase::GetYearsEmployed() 

तुम देखो JIT देखने के लिए कौन सी क्लास में विधि क्रम में स्थित है की जरूरत नहीं है? कंपाइलर को पहले ही पता चला है कि विधि कर्मचारी वर्ग में नहीं है, लेकिन कर्मचारीबेस कक्षा में और एक सही कॉल उत्सर्जित किया गया है। लेकिन रिक्टर के शब्दों से जेआईटी को यह पता लगाना होगा कि विधि वास्तव में रनटाइम पर कर्मचारीबेस कक्षा में स्थित है।

क्या जेफरी रिक्टर ने गलत किया? या मुझे कुछ समझ में नहीं आता?

+7

कोई दूसरे को बाहर नहीं करता है, संकलक जिटर को आसान बनाने के लिए काम करने का हकदार है। Iexe को excompile करने के लिए ildasm.exe का उपयोग करें, कॉल को संशोधित करें और ilasm.exe के साथ वापस संकलित करें। यह अभी भी काम करता है। –

+1

@colinfang - इसके विपरीत, एक व्युत्पन्न कक्षा के लिए विधि टोकन प्राप्त करना संभव है भले ही विधियों को बेस क्लास पर घोषित किया गया हो। इस तरह के तरीकों पर कॉल करना विशेष रूप से सीएलआर स्पेक में कहा जाता है (कम से कम, 'कॉल' ओपोड के लिए; मुझे कोई संकेत नहीं दिखता कि 'कॉलवर्ट' अलग है)। 'आधार' कॉल के लिए, यह वास्तव में पुस्तकालयों को संस्करणित करते समय "भंगुर आधार वर्ग" समस्या को रोक सकता है, और तर्कसंगत रूप से सी # कंपाइलर को इस तरह से कॉल करना चाहिए, हालांकि ऐसा नहीं है। Http://blogs.msdn.com/b/ericlippert/archive/2010/03/29/putting-a-base-in-the-middle.aspx देखें। – kvb

+0

@kvb आपकी टिप्पणी मूल्यवान है और मेरा दूसरा बार प्रयोग (इस बार JustDecompile के लिए प्लगइन की बजाय ilasm का उपयोग करें) साबित हुआ कि आप सही हैं। हालांकि, मैं ईसीएमए 335 में कहीं भी नहीं मिला था। 1. स्पष्ट रूप से एक व्युत्पन्न वर्ग के लिए एक विधि टोकन के अस्तित्व को बताता है जहां आधार वर्ग पर विधियों को घोषित किया जाता है (मुझे विश्वास था कि यह इस्लाम के लॉग के आधार पर 'मेथड्रफ' होगा)। – colinfang

उत्तर

0
मेरी समझ से

, और अपने उदाहरण का उपयोग: हुड के अंतर्गत:

एक आधार वर्ग में एक आभासी विधि एक व्युत्पन्न वर्ग विधि तालिका में एक प्रविष्टि होगा। इसका मतलब है कि 'ऑब्जेक्ट' प्रकार में सभी वर्चुअल विधियां उनकी सभी व्युत्पन्न कक्षा विधि तालिका में उपलब्ध हैं।

(उदाहरण के कोड में के रूप में) एक गैर आभासी विधि, व्युत्पन्न वर्ग में कोई आपूर्ति की कार्यक्षमता के साथ नहीं वास्तव में व्युत्पन्न वर्ग विधि तालिकाओं में एक प्रवेश करना होगा!

यह जांचने के लिए, मैंने प्रबंधक कक्षा के लिए विधि तालिका की जांच करने के लिए WinDbg में कोड चलाया।

MethodDesc तालिका प्रविष्टि MethodDe JIT नाम

506a4960 503a6728 PreJIT System.Object.ToString()

50698790 503a6730 PreJIT System.Object.Equals (System.Object)

50698360 503a6750 PreJIT System.Object.GetHashCode()

506916f0 503a6764 PreJIT System.Object.Finalize()

001b00c8 00,143,904 JIT Manager..ctor()

0014c065 001438f8 कोई नहीं Manager.GenProgressReport()

तो, मैं वस्तु की आभासी वस्तु तरीकों को देख सकते हैं, लेकिन मैं नहीं देख सकते हैं वास्तविक विधि के बाद से GetYearsEmployed यह आभासी नहीं है और इसका कोई क्रियान्वयन नहीं हुआ है। संयोग से, एक ही अवधारणा से, आप व्युत्पन्न कक्षा में SomeOtherMethod फ़ंक्शन को नहीं देख सकते हैं।

हालांकि, आप इन कार्यों को कॉल कर सकते हैं, यह सिर्फ वे विधि तालिका में नहीं हैं। मैं गलत हो सकता था, लेकिन मेरा मानना ​​है कि कॉल स्टैक उन्हें ढूंढने के लिए चला गया है। हो सकता है कि मिस्टर रिचटर का अर्थ उनकी पुस्तक में है। मैं अपनी पुस्तक को पढ़ने के लिए मुश्किल लगता है, लेकिन है कि क्योंकि अवधारणाओं जटिल हैं और उसने मुझे ज्यादा चालाक है :)

मुझे यकीन है कि आईएल समस्या को दर्शाता है नहीं कर रहा हूँ। मेरा मानना ​​है कि यह शायद आईएल के नीचे एक परत है, इसलिए मैंने विंडबग को देखने के लिए उपयोग किया है। मैं तुम्हें देखने के लिए की यह ढेर चलता windbg इस्तेमाल कर सकते हैं लगता है ....

0

समान प्रश्न मैं How is non-virtual instance method inheritance resolved? पोस्ट में @usr द्वारा उत्तर के रूप में:

रनटाइम आमतौर पर अर्थ है "जब/हर कोड चलाता है "। जेआईटी संकल्प यहां कोड चलाने से पहले ही शामिल है। क्या JIT को "रनटाइम पर" कहकर संदर्भित नहीं किया जा रहा है।

जेफरी के शब्दों में

इसके अलावा

JIT कम्पाइलर प्रकार उद्देश्य यह है कि चर के प्रकार से मेल खाती है कॉल करने के लिए इस्तेमाल किया जा रहा स्थित।

वैरिएबल प्रकार का मेरा मानना ​​है कि "मेटाडेटा टोकन द्वारा निर्दिष्ट कक्षा" (ईसीएमए 335 III.3.19 कॉल) जिस पर जेआईटी विधि गंतव्य को हल करता है।

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

class A 
    { 
     public static void Foo() {Console.WriteLine(1); } 
     public void Bar() { Console.WriteLine(2); } 
    } 
    class B : A {} 
    class C : B {} 

    static void Main() 
    { 
     C.Foo(); 
     new C().Bar(); 
     C x = new C(); 
     x.Bar(); 
     Console.ReadKey(); 
    } 

IL_0000: call  void ConsoleApplication5.Program/A::Foo() // change to B::Foo() 
IL_0005: newobj  instance void ConsoleApplication5.Program/C::.ctor() 
IL_000a: call  instance void ConsoleApplication5.Program/A::Bar() // change to B::Bar() 
IL_000f: newobj  instance void ConsoleApplication5.Program/C::.ctor() 
IL_0014: stloc.0 
IL_0015: ldloc.0 
IL_0016: callvirt instance void ConsoleApplication5.Program/A::Bar() // change to B::Bar() 
IL_001b: call  valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey() 
IL_0020: pop 
IL_0021: ret 

अगर हम ILDASM + Ilasm B::Foo() को A::Foo() बदलने के लिए, और B.Bar() को A::Bar() बदलने के लिए उपयोग करते हैं, आवेदन ठीक चलाता है।

2

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

यदि आप गतिशील विधि संकल्प चाहते हैं, तो virtual का उपयोग करें। यदि आप virtual का उपयोग नहीं करते हैं तो आपको पूर्ण स्थिर रिज़ॉल्यूशन मिलता है। आपकी पंसद। ऑब्जेक्ट संदर्भ का रनटाइम प्रकार this पॉइंटर बनता है, गैर-वर्चुअल विधियों के संकल्प में कोई फर्क नहीं पड़ता (न ही सीएससीएक्सई के लिए सीएलआरआईटीई के लिए)।

जेआईटी हमेशा सटीक चुनी विधि को कॉल करेगा। यह एक अपवाद फेंक देगा यदि विधि मौजूद नहीं है (शायद क्योंकि कैली डीएलएल बदल दिया गया था)। यह एक अलग विधि नहीं बुलाएगा।

callvirt गैर-वर्चुअल विधियों को भी कॉल कर सकते हैं। इसका उपयोग शून्य चेक करने के लिए किया जाता है। इसे परिभाषित किया गया है, और सी # को प्रत्येक कॉल पर एक शून्य जांच करने के लिए परिभाषित किया गया है।

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