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