सी # में स्टैक ट्रेस खोए बिना इनर एक्सेप्शन को रीथ्रो कैसे करें?


305

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

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
ऐसा करने का एक और तरीका है जिसमें किसी भी वूडू की आवश्यकता नहीं है। यहाँ उत्तर पर एक नज़र डालें: stackoverflow.com/questions/15668334/…
टिमोथी शील्ड्स

डायनामिकली मेथड थ्रो में फेंका गया अपवाद "अपवाद को एक मंगलाचरण के लक्ष्य द्वारा फेंका गया" अपवाद है। इसका अपना स्टैक ट्रेस है। वहाँ वास्तव में चिंता करने के लिए बहुत कुछ नहीं है।
अंजीह

जवाबों:


481

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

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

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

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

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


11
एक अपवाद के लिए अच्छा उम्मीदवार।रोथ्रो () विस्तार विधि?
nmarler

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

53
आपको throw;.Throw () लाइन के बाद एक नियमित लगाने की आवश्यकता हो सकती है , क्योंकि कंपाइलर को यह नहीं पता होगा कि .Throw () हमेशा एक अपवाद फेंकता है। throw;परिणाम के रूप में कभी नहीं बुलाया जाएगा, लेकिन कम से कम कंपाइलर को शिकायत नहीं होगी यदि आपके विधि को एक रिटर्न ऑब्जेक्ट की आवश्यकता है या एक async फ़ंक्शन है।
टोड

5
@Taudris यह प्रश्न विशेष रूप से आंतरिक अपवाद को पुनर्व्यवस्थित करने के बारे में है, जिसे विशेष रूप से नियंत्रित नहीं किया जा सकता है throw;। यदि आप throw ex.InnerException;स्टैक-ट्रेस का उपयोग करते हैं तो इसे फिर से फेंके जाने वाले बिंदु पर पुन: आरंभीकृत किया जाता है।
पॉल टर्नर

5
@amitjhaExceptionDispatchInfo.Capture(ex.InnerException ?? ex).Throw();
Vedran

86

यह है प्रतिबिंब के बिना 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 ;
}

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

2
वास्तव में, यह आह्वान करने से ज्यादा धीमा नहीं है InternalPreserveStackTrace(10000 पुनरावृत्तियों के साथ लगभग 6% धीमा)। परावर्तन द्वारा सीधे खेतों तक पहुँचना आह्वान की तुलना में लगभग 2.5% अधिक तेज हैInternalPreserveStackTrace
थॉमस लेवेस्क

1
मैं e.Dataएक स्ट्रिंग या एक अद्वितीय ऑब्जेक्ट कुंजी के साथ शब्दकोश का उपयोग करने की सलाह दूंगा ( static readonly object myExceptionDataKey = new object ()लेकिन अगर आपको अपवाद कहीं भी अनुक्रमित करना है तो ऐसा न करें)। संशोधित करने से बचें e.Message, क्योंकि आपके पास कहीं कोड हो सकता है जो पार्स करता है e.Message। पार्सिंग e.Messageबुराई है, लेकिन कोई अन्य विकल्प नहीं हो सकता है, उदाहरण के लिए यदि आपको खराब अपवाद प्रथाओं के साथ 3-पार्टी लाइब्रेरी का उपयोग करना है।
एंटोन टायखी

10
DoFixups कस्टम अपवादों के लिए टूट जाते हैं यदि उनके पास क्रमिक
संचय

3
सुझाए गए समाधान काम नहीं करते हैं यदि अपवाद में क्रमिक निर्माण नहीं है। मैं stackoverflow.com/a/4557183/209727 पर प्रस्तावित समाधान का उपयोग करने का सुझाव देता हूं जो किसी भी मामले में अच्छी तरह से काम करता है। .NET 4.5 के लिए ExceptionDispatchInfo वर्ग का उपयोग करने पर विचार करें।
डेविड इकार्डी

33

मुझे लगता है कि आपका सबसे अच्छा दांव सिर्फ आपके कैच ब्लॉक में इसे लगाना होगा:

throw;

और फिर बाद में अंतरंगता निकालें।


21
या कोशिश / पकड़ को पूरी तरह से हटा दें।
डैनियल इयरविकर

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

12
@ जोर्डन - क्लीन कोड एक अंतिम ब्लॉक में होना चाहिए न कि कैच ब्लॉक होना चाहिए
पाओलो

17
@ पाओलो - अगर इसे हर मामले में निष्पादित किया जाना है, तो हाँ। यदि इसे केवल विफलता के मामले में निष्पादित किया जाना है, तो नहीं।
चिकोडोरो

4
ध्यान रखें कि InternalPreserveStackTrace isnt थ्रेड सुरक्षित है, इसलिए यदि आपके पास इन अपवाद स्थितियों के 2 धागे हैं ... तो भगवान हम सभी पर दया कर सकते हैं।
रोब

13
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 );
    }
}

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


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

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

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

क्यों उपयोग करें Action<Exception>? यहां स्थैतिक विधि का
कीकनेट

13

किसी ने भी 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 के साथ समान है क्योंकि मूल अपवाद की लाइन संख्या संरक्षित है, लेकिन यह वास्तविक पुनरावृत्ति नहीं है क्योंकि यह मूल अपवाद के प्रकार को बदलता है।


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

@JesperMatthiesen मुझसे गलती हो सकती है, लेकिन मैंने सुना है कि यह निर्भर करता है कि क्या अपवाद को फेंक दिया गया था और उसी फ़ाइल में पकड़ा गया था। यदि यह एक ही फ़ाइल है, तो स्टैक ट्रेस खो जाएगा, यदि यह एक और फ़ाइल है, तो इसे संरक्षित किया जाएगा।
जाहू

10

और भी अधिक प्रतिबिंब ...

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;
}

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


28
यह वास्तव में एक बहुत बुरा विचार है, क्योंकि यह फ्रेमवर्क कक्षाओं के बारे में आंतरिक अनिर्धारित विवरणों पर निर्भर करता है।
डैनियल ईयरविकर

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

1
आंतरिक InternalPreserveStackTraceविधि को कॉल करना बेहतर होगा, क्योंकि यह एक ही काम करता है और भविष्य में बदलने की संभावना कम है ...
थॉमस लेवेस्क

1
दरअसल, यह और भी बुरा होगा, क्योंकि मोनो पर इंटरनलप्रेज़र्वस्टैकट्रेस मौजूद नहीं है।
स्कोलिमा

5
@daniel - अच्छी तरह से एक बहुत, वास्तव में, वास्तव में फेंक के लिए बहुत बुरा विचार है; स्टैकट्रेस को रीसेट करने के लिए जब प्रत्येक .net डेवलपर को यह विश्वास नहीं करने के लिए प्रशिक्षित किया जाता है। यह भी एक सच में, वास्तव में, वास्तव में बुरी बात है अगर आप एक NullReferenceException के स्रोत का पता नहीं लगा सकते हैं और एक ग्राहक / आदेश खो सकते हैं क्योंकि आप इसे नहीं ढूंढ सकते हैं। मेरे लिए जो 'अनडॉन्मेंटेड डिटेल्स' को ट्रम्प करता है और निश्चित रूप से मोनो है।
साइमन_वेवर

10

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


5

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

    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
अच्छा विचार है, लेकिन आप हमेशा उस कोड को नियंत्रित नहीं करते हैं जो कॉल करता है .Invoke()
एंटोन टायखी

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

3

अनुपम नमूना कोड जो अपवाद क्रमांकन / deserialization का उपयोग करता है। इसे अनुक्रमिक होने के लिए वास्तविक अपवाद प्रकार की आवश्यकता नहीं होती है। इसके अलावा, यह केवल सार्वजनिक / संरक्षित विधियों का उपयोग करता है।

    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 });
    }

अनुक्रमिक होने के लिए वास्तविक अपवाद प्रकार की आवश्यकता नहीं है?
किकेनेट

3

पॉल टर्नर जवाब के आधार पर मैंने एक विस्तार विधि बनाई

    public static Exception Capture(this Exception ex)
    {
        ExceptionDispatchInfo.Capture(ex).Throw();
        return ex;
    }

return exIST तक पहुँच कभी नहीं लेकिन लाभ यह है कि मैं का उपयोग कर सकते है throw ex.Capture()एक एक लाइनर के रूप में तो संकलक एक से आगे नहीं बढ़ेगी not all code paths return a valueत्रुटि।

    public static object InvokeEx(this MethodInfo method, object obj, object[] parameters)
    {
        {
            return method.Invoke(obj, parameters);
        }
        catch (TargetInvocationException ex) when (ex.InnerException != null)
        {
            throw ex.InnerException.Capture();
        }
    }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.