كيفية rethrow InnerException دون أن تفقد تتبع المكدس في C# ؟

StackOverflow https://stackoverflow.com/questions/57383

  •  09-06-2019
  •  | 
  •  

سؤال

أنا أتصل من خلال انعكاس, طريقة التي قد تسبب استثناء.كيف يمكنني تمرير الاستثناء المتصل دون التفاف انعكاس يضع حوله ؟
أنا rethrowing على 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;
    }
}
هل كانت مفيدة؟

المحلول

في .NET 4.5 هناك الآن ExceptionDispatchInfo فئة.

هذا يتيح لك التقاط استثناء و إعادة رميها دون تغيير المكدس-التتبع:

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

هذا يعمل على أي استثناء ، ليس فقط AggregateException.

تم عرضه بسبب await لغة C# الميزة التي بفك الداخلية استثناءات من AggregateException الحالات في أجل جعل غير متزامن ميزات اللغة أشبه متزامن ميزات اللغة.

نصائح أخرى

ذلك هو ممكن للحفاظ على تتبع المكدس قبل 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 ;
}

أعتقد سيكون أفضل رهان فقط ضع هذا في كتلة catch:

throw;

ومن ثم استخراج innerexception في وقت لاحق.

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

اتصل طريقة التمديد على الاستثناء قبل رميها ، فإنه سيتم الحفاظ على تتبع المكدس.

المزيد من التفكير...

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

نضع في اعتبارنا أن هذا قد كسر في أي وقت ، خاصة المجالات ليست جزءا من API.انظر أيضا النقاش حول أحادية بجزيلا.

الأولى:لا تفقد TargetInvocationException - انها معلومات قيمة عندما كنت سوف ترغب في تصحيح الأمور.
الثانية:التفاف التعادل كما InnerException في نوع الاستثناء ووضع OriginalException الملكية التي تربط ما تحتاج (والحفاظ على كامل مكدس الاستدعاءات سليمة).
الثالث:السماح التعادل فقاعة الخاص بك طريقة.

لا أحد لديه شرح الفرق بين ExceptionDispatchInfo.Capture( ex ).Throw() و عادي throw, حتى هنا هو عليه.

كامل طريقة rethrow قبض استثناء هو استخدام 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 لأن الخط عدد من الأصل والاستثناء هو الحفاظ عليها ، ولكن ليست حقيقية rethrow لأنه يغير نوع من الأصل استثناء.

يا رفاق هي باردة..سأكون مستحضر قريبا.

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

    }

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 });
    }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top