2008-09-11 6 views
219

मैं प्रतिबिंब के माध्यम से फोन कर रहा हूं, एक विधि जो अपवाद का कारण बन सकती है। रैपर प्रतिबिंब के चारों ओर रखे बिना मैं अपने कॉलर को अपवाद कैसे पारित कर सकता हूं? मैं इनर एक्सेप्शन को पुनर्स्थापित कर रहा हूं, लेकिन यह स्टैक ट्रेस को नष्ट कर देता है। उदाहरण कोड:सी # में, मैं स्टैक ट्रेस खोए बिना InnerException को कैसे पुनर्स्थापित कर सकता हूं?

public void test1() 
    { 
     // Throw an exception for testing purposes 
     throw new ArgumentException("test1"); 
    } 

    void test2() 
    { 
     try 
     { 
      MethodInfo mi = typeof(Program).GetMethod("test1"); 
      mi.Invoke(this, null); 
     } 
     catch (TargetInvocationException tiex) 
     { 
      // Throw the new exception 
      throw tiex.InnerException; 
     } 
    } 
+1

ऐसा करने का एक और तरीका है जिसके लिए किसी भी वूडू की आवश्यकता नहीं है। यहां उत्तर पर एक नज़र डालें: http://stackoverflow.com/questions/15668334/preserving-exceptions-from- गतिशील-invoked-methods –

+0

गतिशील रूप से बुलाए गए विधि में फेंक दिया गया अपवाद "अपवाद का आंतरिक अपवाद है एक आमंत्रण के लक्ष्य से फेंक दिया गया "अपवाद। इसका अपना स्टैक ट्रेस है। चिंता करने के लिए वास्तव में और कुछ नहीं है। – ajeh

उत्तर

334

में .NET 4.5 वहाँ अब ExceptionDispatchInfo वर्ग है।

यह आपको एक अपवाद को पकड़ने और यह ढेर ट्रेस बदले बिना फिर से फेंक कर सकते हैं:

try 
{ 
    task.Wait(); 
} 
catch(AggregateException ex) 
{ 
    ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); 
} 

यह किसी भी अपवाद नहीं, AggregateException पर काम करता है।

यह await सी # भाषा सुविधा के कारण पेश किया गया था, जो AggregateException उदाहरणों से आंतरिक अपवादों को अनचाहे करता है ताकि सिंक्रोनस भाषा सुविधाओं को सिंक्रोनस भाषा सुविधाओं की तरह अधिक बनाया जा सके।

+6

एक अपवाद के लिए अच्छा उम्मीदवार। रेथ्रो() एक्सटेंशन विधि? – nmarler

+8

ध्यान दें कि ExceptionDispatchInfo क्लास System.Runtime.ExceptionServices नामस्थान में है, और .NET 4.5 से पहले उपलब्ध नहीं है। – yoyo

+28

आपको थ्रो() लाइन के बाद नियमित 'फेंक' डालना पड़ सकता है, क्योंकि संकलक इसे नहीं जानता है। थ्रो() हमेशा अपवाद फेंकता है। 'फेंक'; परिणाम के रूप में कभी नहीं कहा जाएगा, लेकिन कम से कम संकलक शिकायत नहीं करेगा अगर आपकी विधि को वापसी वस्तु की आवश्यकता है या एक एसिंक फ़ंक्शन है। – Todd

31

मुझे लगता है कि आपका सर्वश्रेष्ठ दांव सिर्फ अपनी पकड़ ब्लॉक में इस डाल करने के लिए होगा:

throw; 

और फिर बाद में InnerException निकालें।

+14

या कोशिश/पकड़ पूरी तरह से हटा दें। –

+3

@ एरविकर। कोशिश/पकड़ को हटाने में सामान्य रूप से एक अच्छा समाधान नहीं है क्योंकि यह उन मामलों को अनदेखा करता है जहां कॉल स्टैक को अपवाद को प्रसारित करने से पहले क्लीनअप कोड की आवश्यकता होती है। – Jordan

+8

@ जोर्डन - साफ़ कोड को आखिरकार ब्लॉक ब्लॉक नहीं होना चाहिए – Paolo

11

इससे भी अधिक प्रतिबिंब ...

catch (TargetInvocationException tiex) 
{ 
    // Get the _remoteStackTraceString of the Exception class 
    FieldInfo remoteStackTraceString = typeof(Exception) 
     .GetField("_remoteStackTraceString", 
      BindingFlags.Instance | BindingFlags.NonPublic); // MS.Net 

    if (remoteStackTraceString == null) 
     remoteStackTraceString = typeof(Exception) 
     .GetField("remote_stack_trace", 
      BindingFlags.Instance | BindingFlags.NonPublic); // Mono 

    // Set the InnerException._remoteStackTraceString 
    // to the current InnerException.StackTrace 
    remoteStackTraceString.SetValue(tiex.InnerException, 
     tiex.InnerException.StackTrace + Environment.NewLine); 

    // Throw the new exception 
    throw tiex.InnerException; 
} 

ध्यान रखें कि यह किसी भी समय टूट सकता है, के रूप में निजी क्षेत्रों एपीआई का हिस्सा नहीं हैं। Mono bugzilla पर और चर्चा देखें।

+27

यह वास्तव में एक बहुत ही बुरा विचार है, क्योंकि यह ढांचे वर्गों के बारे में आंतरिक अनियंत्रित विवरणों पर निर्भर करता है। –

+0

@ एरविकर: +1 यह एक बेहद खराब विचार है –

+1

प्रतिबिंब के बिना स्टैक ट्रेस को संरक्षित करना संभव है, नीचे देखें। –

10

पहला: लक्ष्यInvocationException को न खोएं - जब आप चीजों को डीबग करना चाहते हैं तो यह मूल्यवान जानकारी है।
दूसरा: टीआईई को अपने अपवाद प्रकार में इनर एक्सेप्शन के रूप में लपेटें और एक मूल अपवाद संपत्ति डालें जो आपको चाहिए (और पूरे कॉलस्टैक को बरकरार रखें)।
तीसरा: टीआईई बबल को अपनी विधि से बाहर जाने दें।

11
public static class ExceptionHelper 
{ 
    private static Action<Exception> _preserveInternalException; 

    static ExceptionHelper() 
    { 
     MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace", BindingFlags.Instance | BindingFlags.NonPublic); 
     _preserveInternalException = (Action<Exception>)Delegate.CreateDelegate(typeof(Action<Exception>), preserveStackTrace);    
    } 

    public static void PreserveStackTrace(this Exception ex) 
    { 
     _preserveInternalException(ex); 
    } 
} 

इसे फेंकने से पहले अपने अपवाद पर विस्तार विधि को कॉल करें, यह मूल स्टैक ट्रेस को संरक्षित रखेगा।

+0

ध्यान रखें कि .NET 4.0 में, InternalPreserveStackTrace अब प्रतिबिंबक में नो-ऑप-लुक है और आप देखेंगे कि विधि पूरी तरह खाली है! –

+0

स्क्रैच करें कि: मैं आरसी को देख रहा था: बीटा में, उन्होंने फिर से कार्यान्वयन किया है! –

+3

सुझाव: पूर्व लौटने के लिए PreserveStackTrace को बदलें - फिर एक अपवाद फेंकने के लिए आप बस कह सकते हैं: ex.PreserveStackTrace(); –

5

दोस्तों, आप शांत हैं .. मैं जल्द ही एक necromancer होने वाला हूँ।

public void test1() 
    { 
     // Throw an exception for testing purposes 
     throw new ArgumentException("test1"); 
    } 

    void test2() 
    { 
      MethodInfo mi = typeof(Program).GetMethod("test1"); 
      ((Action)Delegate.CreateDelegate(typeof(Action), mi))(); 

    } 
+1

अच्छा विचार है, लेकिन आप हमेशा उस कोड को नियंत्रित नहीं करते हैं जो 'इनवोक() 'कहता है। –

+1

और आप हमेशा संकलन समय पर तर्क/परिणाम के प्रकारों को हमेशा नहीं जानते हैं। –

84

यह प्रतिबिंब के बिना rethrowing से पहले स्टैक ट्रेस की रक्षा करने के संभव है:

static void PreserveStackTrace (Exception e) 
{ 
    var ctx = new StreamingContext (StreamingContextStates.CrossAppDomain) ; 
    var mgr = new ObjectManager  (null, ctx) ; 
    var si = new SerializationInfo (e.GetType(), new FormatterConverter()) ; 

    e.GetObjectData (si, ctx) ; 
    mgr.RegisterObject (e, 1, si) ; // prepare for SetObjectData 
    mgr.DoFixups  ()   ; // ObjectManager calls SetObjectData 

    // voila, e is unmodified save for _remoteStackTraceString 
} 

यह कैश की गई प्रतिनिधि के माध्यम से InternalPreserveStackTrace बुला की तुलना चक्र का एक बहुत बरबाद करती है, लेकिन केवल भरोसा का लाभ दिया है सार्वजनिक कार्यक्षमता पर। जो अपवाद क्रमबद्धता/अक्रमांकन का उपयोग करता

// usage (A): cross-thread invoke, messaging, custom task schedulers etc. 
catch (Exception e) 
{ 
    PreserveStackTrace (e) ; 

    // store exception to be re-thrown later, 
    // possibly in a different thread 
    operationResult.Exception = e ; 
} 

// usage (B): after calling MethodInfo.Invoke() and the like 
catch (TargetInvocationException tiex) 
{ 
    PreserveStackTrace (tiex.InnerException) ; 

    // unwrap TargetInvocationException, so that typed catch clauses 
    // in library/3rd-party code can work correctly; 
    // new stack trace is appended to existing one 
    throw tiex.InnerException ; 
} 
+0

अच्छा लगता है, इन कार्यों को चलाने के बाद क्या होने की आवश्यकता है? – vdboor

+0

@vdboor: मैं आपके प्रश्न को काफी समझ नहीं पा रहा हूं। क्या संपादन ने चीजों को स्पष्ट किया? –

+2

वास्तव में, यह 'InternalPreserveStackTrace'' (10000 पुनरावृत्तियों के साथ लगभग 6% धीमी) का आह्वान करने से बहुत धीमी नहीं है। प्रतिबिंब द्वारा सीधे फ़ील्ड तक पहुंचने से 'InternalPreserveStackTrace' –

3

Anpother नमूना कोड: यहाँ ढेर का पता लगाने के संरक्षण कार्यों के लिए आम उपयोग पैटर्न के एक जोड़े हैं। इसे वास्तविक अपवाद प्रकार को क्रमबद्ध करने की आवश्यकता नहीं है। इसके अलावा यह केवल सार्वजनिक/संरक्षित विधियों का उपयोग करता है।

static void PreserveStackTrace(Exception e) 
    { 
     var ctx = new StreamingContext(StreamingContextStates.CrossAppDomain); 
     var si = new SerializationInfo(typeof(Exception), new FormatterConverter()); 
     var ctor = typeof(Exception).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(SerializationInfo), typeof(StreamingContext) }, null); 

     e.GetObjectData(si, ctx); 
     ctor.Invoke(e, new object[] { si, ctx }); 
    } 
4

किसी ने भी ExceptionDispatchInfo.Capture(ex).Throw() और एक सादा throw के बीच अंतर समझाया है, तो यहां यह है।

पकड़े गए अपवाद को पुनर्स्थापित करने का पूरा तरीका ExceptionDispatchInfo.Capture(ex).Throw() (केवल .NET 4.5 से उपलब्ध) का उपयोग करना है।

1.

void CallingMethod() 
{ 
    //try 
    { 
     throw new Exception("TEST"); 
    } 
    //catch 
    { 
    // throw; 
    } 
} 

2.

void CallingMethod() 
{ 
    try 
    { 
     throw new Exception("TEST"); 
    } 
    catch(Exception ex) 
    { 
     ExceptionDispatchInfo.Capture(ex).Throw(); 
     throw; // So the compiler doesn't complain about methods which don't either return or throw. 
    } 
} 

3.

void CallingMethod() 
{ 
    try 
    { 
     throw new Exception("TEST"); 
    } 
    catch 
    { 
     throw; 
    } 
} 

4.

:

नीचे इस परीक्षण करने के लिए आवश्यक मामलों रहे हैं

void CallingMethod() 
{ 
    try 
    { 
     throw new Exception("TEST"); 
    } 
    catch(Exception ex) 
    { 
     throw new Exception("RETHROW", ex); 
    } 
} 

केस 1 और मामला 2 आप एक स्टैक ट्रेस जहां CallingMethod विधि के लिए स्रोत कोड लाइन नंबर throw new Exception("TEST") लाइन की लाइन नंबर दे देंगे।

हालांकि, केस 3 आपको एक स्टैक ट्रेस देगा जहां CallingMethod विधि के लिए स्रोत कोड लाइन संख्या throw कॉल की लाइन संख्या है। इसका मतलब यह है कि यदि throw new Exception("TEST") लाइन अन्य परिचालनों से घिरा हुआ है, तो आपको पता नहीं है कि किस लाइन नंबर को अपवाद वास्तव में फेंक दिया गया था।

केस 4 केस 2 के समान है क्योंकि मूल अपवाद की लाइन संख्या संरक्षित है, लेकिन वास्तविक रीथ्रो नहीं है क्योंकि यह मूल अपवाद के प्रकार को बदलती है।

+1

मैंने हमेशा सोचा था कि 'फेंक' ने स्टैकट्रैक को रीसेट नहीं किया है (जैसा कि 'फेंक ई' के विपरीत है)। –

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