2014-06-04 7 views
5

पर NullReferenceException के साथ निम्न क्रैश क्यों होता है?मूल्यांकन के सदस्य संदर्भ और असाइनमेंट ऑर्डर

class A { 
    public B b; 
} 

class B { 
    public int c; 
    public int other, various, fields; 
} 

class Program { 

    private static int LazyInitBAndReturnValue(A a) 
    { 
     if (a.b == null) 
      a.b = new B(); 

     return 42; 
    } 

    static void Main(string[] args) 
    { 
     A a = new A(); 
     a.b.c = LazyInitBAndReturnValue(a); 
     a.b.other = LazyInitBAndReturnValue(a); 
     a.b.various = LazyInitBAndReturnValue(a); 
     a.b.fields = LazyInitBAndReturnValue(a); 
    } 
} 

असाइनमेंट भाव from right to left मूल्यांकन किया जाता है, जब तक हम a.b.c को बताए जाते हैं द्वारा हां, तो a.b शून्य नहीं होना चाहिए। विचित्र रूप से पर्याप्त, जब अपवाद पर डीबगर टूट जाता है, तो यह भी गैर-शून्य मूल्य के आरंभ में a.b दिखाता है।

Debugger on break

+0

'b' को' a.b.c' में संदर्भित किया गया है, इससे पहले इसे 'LazyInitB ...() 'में प्रारंभ किया गया है। – ja72

+0

टिप्पणी करने से पहले जवाब पढ़ें, डीबगर व्यवहार ऐसा होने से कहीं अधिक जटिल दिखाना प्रतीत होता है। – SamStephens

उत्तर

2

यह सी # कल्पना की Section 7.13.1 में विस्तृत है।

प्रपत्र एक्स = y निम्न चरण शामिल हैं का एक सरल कार्य के रन-टाइम प्रसंस्करण:

  • एक्स एक चर के रूप में वर्गीकृत किया जाता है:
    • एक्स के लिए मूल्यांकन किया जाता है चर का उत्पादन करें।
    • वाई का मूल्यांकन किया गया है और, यदि आवश्यक हो, तो एक अंतर्निहित रूपांतरण (धारा 6.1) के माध्यम से एक्स के प्रकार में परिवर्तित किया गया है।
    • यदि एक्स द्वारा दिया गया चर संदर्भ-प्रकार का एक सरणी तत्व है, तो यह सुनिश्चित करने के लिए एक रन-टाइम चेक किया जाता है कि y12 के लिए गणना की गई मान सरणी उदाहरण के साथ संगत है, जो x तत्व है। यदि y शून्य है, तो चेक सफल होता है, या यदि कोई अंतर्निहित संदर्भ रूपांतरण (धारा 6.1.4) उदाहरण के वास्तविक प्रकार से मौजूद होता है जो y द्वारा संदर्भित सरणी उदाहरण के वास्तविक तत्व प्रकार से x है। अन्यथा, एक System.ArrayTypeMismatchException फेंक दिया गया है।
    • वाई के मूल्यांकन और रूपांतरण से उत्पन्न मूल्य x के मूल्यांकन द्वारा दिए गए स्थान में संग्रहीत किया जाता है।
  • एक्स एक संपत्ति या इंडेक्सर पहुँच के रूप में वर्गीकृत किया जाता है:
    • उदाहरण अभिव्यक्ति (अगर एक्स स्थिर नहीं है) और तर्क सूची (एक्स एक इंडेक्सर पहुँच है अगर) x के साथ जुड़े मूल्यांकन किया जाता है, और परिणाम बाद के सेट एक्सेसर आमंत्रण में उपयोग किया जाता है।
    • वाई का मूल्यांकन किया गया है और, यदि आवश्यक हो, तो एक अंतर्निहित रूपांतरण (धारा 6.1) के माध्यम से एक्स के प्रकार में परिवर्तित किया गया है।
    • एक्स के सेट एक्सेसर को y के मानकी मान के रूप में गणना के मान के साथ बुलाया जाता है।

मुझे लगता है कि नीचे धारा (अगर एक्स एक संपत्ति या इंडेक्सर पहुँच के रूप में वर्गीकृत किया जाता है) एक संकेत प्रदान करता है, लेकिन शायद एक सी # विशेषज्ञ स्पष्ट कर सकते हैं।

एक सेट एक्सेसर पहले उत्पन्न होता है, तो y मूल्यांकन किया जाता है (अपने ब्रेकप्वाइंट ट्रिगर), तो सेट एक्सेसर शुरू हो जाती है, जो एक अशक्त संदर्भ अपवाद का कारण बनता है। अगर मुझे लगता है, तो मैं एक्सेसर अंक b के पुराने मान पर इंगित करता हूं, जो शून्य था।जब आप b अपडेट करते हैं, तो यह पहले से बनाए गए एक्सेसर को अपडेट नहीं करता है।

+1

अजीब चीज यह है कि यदि आप वास्तव में कोड को डीबग करते हैं तो विधि 'LazyInitBAndReturnValue' वास्तव में * अपवाद को फेंकने से पहले * चलाता है। मुझे लगता है कि ए.बी. के मूल्यांकन से विधि चलाने से पहले अपवाद को फेंक दिया जाएगा क्योंकि 'सी' का मूल्यांकन नहीं किया जा सका। –

+0

@CraigW। - मुझे स्टैक ओवरफ्लो पर * अनुमान * उत्तरों जोड़ने से नफरत है, लेकिन हो सकता है कि एरिक लिपर्ट इस प्रश्न से गुज़र जाए और मेरे संदेह की पुष्टि या अस्वीकार कर दे। प्रश्न के लिए '1' हालांकि! –

+0

मुझे आपके पिछले अनुच्छेद को दो बार पढ़ना पड़ा था, लेकिन अब मैं जो कह रहा हूं उसे प्राप्त करता हूं। जब तक असाइनमेंट होता है तब तक 'बी' में वास्तव में एक मान होता है लेकिन * आंतरिक रूप से * सीएलआर पहले ही विधि से चलने से पहले * बी' के संदर्भ को संग्रहीत कर चुका है। जब मैंने पहली बार ओपी के कोड को देखा तो मैंने सोचा, "निश्चित रूप से आपको एक NullRefEx प्राप्त करने जा रहे हैं" लेकिन फिर कोड चलाने से मुझे अपना सिर खरोंच कर दिया गया। अब यह (तरह का) समझ में आता है लेकिन मैं निश्चित रूप से ओपी के भ्रम को समझ सकता हूं। –

-1

मुझे एहसास है कि यह आपके प्रश्न का उत्तर नहीं देता है, लेकिन इस ए में कक्षा ए से संबंधित एक सदस्य को शुरू करने के लिए क्लास ए के बाहर कुछ इजाजत देने के लिए मुझे लगता है कि मुझे encapsulation तोड़ने लगता है। यदि बी को पहले उपयोग पर प्रारंभ करने की आवश्यकता है तो बी के "मालिक" को ऐसा करने वाला होना चाहिए।

class A 
{ 
    private B _b; 
    public B b 
    { 
     get 
     { 
      _b = _b ?? new B(); 
      return _b; 
     } 
    } 
} 
+0

मुझे नहीं लगता कि यह कुछ है @ मैट क्लाइन वास्तविक जीवन में करना चाहता है - कम से कम मुझे आशा है कि नहीं। कोड में कई अन्य विरोधी पैटर्न हैं। हालांकि यह अकादमिक परिप्रेक्ष्य से एक दिलचस्प व्यवहार है। नीचे मतदान किया गया क्योंकि यह प्रश्न का उत्तर नहीं है - यह एक टिप्पणी के रूप में उपयुक्त होगा। – SamStephens

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