أفضل الممارسات لالتقاط استثناءات .NET وإعادة طرحها

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

سؤال

ما هي أفضل الممارسات التي يجب مراعاتها عند التقاط الاستثناءات وإعادة طرحها؟أريد التأكد من أن Exception أشياء InnerException ويتم الحفاظ على تتبع المكدس.هل هناك فرق بين كتل التعليمات البرمجية التالية في طريقة تعاملها مع هذا؟

try
{
    //some code
}
catch (Exception ex)
{
    throw ex;
}

ضد:

try
{
    //some code
}
catch
{
    throw;
}
هل كانت مفيدة؟

المحلول

طريقة الحفاظ على تتبع المكدس هي من خلال استخدام ملف throw; وهذا صحيح أيضًا

try {
  // something that bombs here
} catch (Exception ex)
{
    throw;
}

throw ex; يشبه في الأساس طرح استثناء من تلك النقطة، وبالتالي فإن تتبع المكدس سيذهب فقط إلى المكان الذي تُصدر فيه ملف throw ex; إفادة.

مايك صحيح أيضًا، بافتراض أن الاستثناء يسمح لك بتمرير استثناء (وهو أمر مستحسن).

كارل سيجين لديه كتابة رائعة حول معالجة الاستثناءات في أساسيات البرمجة الكتاب الإلكتروني كذلك، وهي قراءة عظيمة.

يحرر:رابط العمل على أسس البرمجة بي دي إف.فقط ابحث في النص عن "الاستثناء".

نصائح أخرى

إذا قمت بإلقاء استثناء جديد مع الاستثناء الأولي، فسوف تحافظ على تتبع المكدس الأولي أيضًا.

try{
} 
catch(Exception ex){
     throw new MoreDescriptiveException("here is what was happening", ex);
}

في الواقع، هناك بعض المواقف التي throw لن تحافظ العبارة على معلومات StackTrace.على سبيل المثال، في الكود أدناه:

try
{
  int i = 0;
  int j = 12 / i; // Line 47
  int k = j + 1;
}
catch
{
  // do something
  // ...
  throw; // Line 54
}

سيشير StackTrace إلى أن السطر 54 قد أثار الاستثناء، على الرغم من أنه تم رفعه في السطر 47.

Unhandled Exception: System.DivideByZeroException: Attempted to divide by zero.
   at Program.WithThrowIncomplete() in Program.cs:line 54
   at Program.Main(String[] args) in Program.cs:line 106

في مواقف مثل تلك الموضحة أعلاه، هناك خياران لحفظ StackTrace الأصلي:

استدعاء Exception.InternalPreserveStackTrace

نظرًا لأنها طريقة خاصة، فيجب استدعاؤها باستخدام الانعكاس:

private static void PreserveStackTrace(Exception exception)
{
  MethodInfo preserveStackTrace = typeof(Exception).GetMethod("InternalPreserveStackTrace",
    BindingFlags.Instance | BindingFlags.NonPublic);
  preserveStackTrace.Invoke(exception, null);
}

لدي عيب في الاعتماد على طريقة خاصة للحفاظ على معلومات StackTrace.يمكن تغييره في الإصدارات المستقبلية من .NET Framework.تم استخراج مثال التعليمات البرمجية أعلاه والحل المقترح أدناه من مدونة فابريس مارجوري.

استدعاء Exception.SetObjectData

تم اقتراح التقنية أدناه بواسطة انطون تيخي كإجابة على في C#، كيف يمكنني إعادة طرح InnerException دون فقدان تتبع المكدس سؤال.

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 
} 

على الرغم من أنها تتمتع بميزة الاعتماد على الأساليب العامة فقط، إلا أنها تعتمد أيضًا على مُنشئ الاستثناء التالي (الذي لا تنفذه بعض الاستثناءات التي طورتها جهات خارجية):

protected Exception(
    SerializationInfo info,
    StreamingContext context
)

في حالتي، كان علي أن أختار النهج الأول، لأن الاستثناءات التي أثارتها مكتبة الطرف الثالث التي كنت أستخدمها لم تنفذ هذا المُنشئ.

عندما انت throw ex, ، فأنت تطرح استثناءً جديدًا بشكل أساسي، وستفقد معلومات تتبع المكدس الأصلية. throw هي الطريقة المفضلة.

القاعدة الأساسية هي تجنب الإمساك بالأشياء الأساسية ورميها Exception هدف.وهذا يفرض عليك أن تكون أكثر ذكاءً فيما يتعلق بالاستثناءات؛وبعبارة أخرى يجب أن يكون لديك صيد واضح ل SqlException حتى لا يحدث رمز التعامل الخاص بك شيئًا خاطئًا مع ملف NullReferenceException.

في العالم الحقيقي رغم ذلك، اصطياد والتسجيل يعد الاستثناء الأساسي أيضًا ممارسة جيدة، لكن لا تنسَ الاطلاع على الأمر برمته للحصول على أي استثناء InnerExceptions قد يكون.

لم يشرح أحد الفرق بين ExceptionDispatchInfo.Capture( ex ).Throw() وسهل 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 لأنه يتم الاحتفاظ برقم سطر الاستثناء الأصلي، ولكنها ليست إعادة طرح حقيقية لأنها تغير نوع الاستثناء الأصلي.

لقد فات عدد قليل من الأشخاص بالفعل نقطة مهمة جدًا - قد تفعل "رمي" و"رمية سابقة" نفس الشيء ولكنها لا تعطيك جزءًا مهمًا من المعلومات وهو الخط الذي حدث فيه الاستثناء.

خذ بعين الاعتبار الكود التالي:

static void Main(string[] args)
{
    try
    {
        TestMe();
    }
    catch (Exception ex)
    {
        string ss = ex.ToString();
    }
}

static void TestMe()
{
    try
    {
        //here's some code that will generate an exception - line #17
    }
    catch (Exception ex)
    {
        //throw new ApplicationException(ex.ToString());
        throw ex; // line# 22
    }
}

عندما تقوم بإجراء "رمية" أو "رمية سابقة"، فإنك تحصل على تتبع المكدس ولكن السطر # سيكون #22 لذا لا يمكنك معرفة الخط الذي كان بالضبط يرمي الاستثناء (ما لم يكن لديك 1 أو عدد قليل فقط أسطر من التعليمات البرمجية في كتلة المحاولة).للحصول على السطر المتوقع رقم 17 في الاستثناء الخاص بك، سيتعين عليك طرح استثناء جديد باستخدام تتبع مكدس الاستثناء الأصلي.

يجب عليك دائمًا استخدام "رمي" ؛ لإعادة تجديد الاستثناءات في .NET ،

الرجوع إلى هذا،http://weblogs.asp.net/bhouse/archive/2004/11/30/272297.aspx

في الأساس، يحتوي MSIL (CIL) على تعليمتين - "رمي" و"إعادة طرح":

  • C#"رمي السابقين ؛" يتم تجميعها في "رمي" Msil
  • C#"رمي" ؛ - في Msil "Rethrow"!

في الأساس أستطيع أن أرى السبب وراء تجاوز "رمي السابقين" لتتبع المكدس.

يمكنك أيضًا استخدام:

try
{
// Dangerous code
}
finally
{
// clean up, or do nothing
}

وأي استثناءات يتم طرحها سوف ترتفع إلى المستوى التالي الذي يتعامل معها.

سأستخدم بالتأكيد:

try
{
    //some code
}
catch
{
    //you should totally do something here, but feel free to rethrow
    //if you need to send the exception up the stack.
    throw;
}

سيؤدي ذلك إلى الحفاظ على مجموعتك.

لمعلوماتك ، لقد اختبرت هذا وتتبع المكدس الذي أبلغ عنه "رمي" ؛ ليس تتبع المكدس الصحيح تماما.مثال:

    private void foo()
    {
        try
        {
            bar(3);
            bar(2);
            bar(1);
            bar(0);
        }
        catch(DivideByZeroException)
        {
            //log message and rethrow...
            throw;
        }
    }

    private void bar(int b)
    {
        int a = 1;
        int c = a/b;  // Generate divide by zero exception.
    }

يشير تتبع المكدس إلى أصل الاستثناء بشكل صحيح (رقم السطر المُبلغ عنه) ولكن رقم السطر المُبلغ عنه لـ foo() هو سطر الرمي؛العبارة، وبالتالي لا يمكنك معرفة أي من الاستدعاءات إلى bar() تسببت في الاستثناء.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top