c# شرح هل هناك فرق بين "رمي" و "رمي السابقين"؟




what is an exception how exceptions are handled in c#? (8)

هناك بعض المشاركات التي تسأل عن الفرق بين هذين الأمرين بالفعل.
(لماذا علي أن أذكر هذا ...)

لكن سؤالي مختلف بطريقة أدعوها "رمي" في طريقة أخرى خاطئة تعامل الله .

public class Program
{
    public static void Main(string[] args)
    {
        try
        {
            // something
        }
        catch (Exception ex)
        {
            HandleException(ex);
        }
    }

    private static void HandleException(Exception ex)
    {
        if (ex is ThreadAbortException)
        {
            // ignore then,
            return;
        }

        if (ex is ArgumentOutOfRangeException)
        {
            // Log then,
            throw ex;
        }

        if (ex is InvalidOperationException)
        {
            // Show message then,
            throw ex;
        }

        // and so on.
    }
}

إذا تم استخدام " try & catch في " Main ، فحينئذٍ يمكنني استخدام throw; لإعادة الخطأ. ولكن في الرمز المبسط أعلاه ، تمر جميع الاستثناءات بـ HandleException

لا throw ex; له نفس التأثير مثل throw الاتصال عند استدعاء داخل HandleException ؟


لتزويدك بمنظور مختلف حول هذا ، فإن استخدام ميزة "رمي" مفيد بشكل خاص إذا كنت تقدم واجهة برمجة تطبيقات إلى عميل وتريد تقديم معلومات تتبع مكدس مطول لمكتبتك الداخلية. باستخدام رمي هنا ، يمكنني الحصول على تتبع المكدس في هذه الحالة من مكتبة System.IO.File File.Delete. إذا استخدمت رمية سابقة ، فلن يتم تمرير هذه المعلومات إلى المعالج الخاص بي.

    static void Main(string[] args)
    {            
       Method1();            
    }
    static void Method1()
    {
        try
        {
            Method2();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception in Method1");             
        }
    }
    static void Method2()
    {
        try
        {
            Method3();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception in Method2");
            Console.WriteLine(ex.TargetSite);
            Console.WriteLine(ex.StackTrace);
            Console.WriteLine(ex.GetType().ToString());
        }
    }
    static void Method3()
    {
        Method4();
    }
    static void Method4()
    {
        try
        {
            System.IO.File.Delete("");
        }
        catch (Exception ex)
        {
            // Displays entire stack trace into the .NET or custom library to Method2() where exception handled
            // If you want to be able to get the most verbose stack trace into the internals of the library you're calling
            throw;                
            // throw ex;
            // Display the stack trace from Method4() to Method2() where exception handled
        }
    }

int a = 0;
        try
        {
            int x = 4;
            int y ;
            try
            {
                y = x / a;
            }


            catch (Exception e)
            {
                Console.WriteLine("inner ex");
                //throw;   // Line 1
                //throw e;   // Line 2
                //throw new Exception("devide by 0");  // Line 3
            }

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            throw ex;
        }
  1. إذا تم التعليق على جميع الخطوط 1 و 2 و 3 - الإخراج - الداخلية السابقين

  2. إذا تم التعليق على كل السطر 2 و 3 - الإخراج - الداخلي السابق System.DevideByZeroException: {"تمت محاولة التقسيم بواسطة صفر."} ---------

  3. إذا تم التعليق على كل السطر 1 و 2 - الإخراج - الداخلي السابق System.Exception: devide by 0 ----

  4. إذا تم التعليق على كل السطر 1 و 3 - الإخراج - الداخلي السابق System.DevideByZeroException: {"تمت محاولة التقسيم على صفر."} --------- وستتم إعادة تعيين StackTrace في حالة التخلص السابق ؛


الإجابات الأخرى صحيحة تمامًا ، لكن هذه الإجابة توفر بعض الحلول الإضافية ، على ما أعتقد.

خذ بعين الاعتبار هذا المثال:

using System;

static class Program
{
  static void Main()
  {
    try
    {
      ThrowTest();
    }
    catch (Exception e)
    {
      Console.WriteLine("Your stack trace:");
      Console.WriteLine(e.StackTrace);
      Console.WriteLine();
      if (e.InnerException == null)
      {
        Console.WriteLine("No inner exception.");
      }
      else
      {
        Console.WriteLine("Stack trace of your inner exception:");
        Console.WriteLine(e.InnerException.StackTrace);
      }
    }
  }

  static void ThrowTest()
  {
    decimal a = 1m;
    decimal b = 0m;
    try
    {
      Mult(a, b);  // line 34
      Div(a, b);   // line 35
      Mult(b, a);  // line 36
      Div(b, a);   // line 37
    }
    catch (ArithmeticException arithExc)
    {
      Console.WriteLine("Handling a {0}.", arithExc.GetType().Name);

      //   uncomment EITHER
      //throw arithExc;
      //   OR
      //throw;
      //   OR
      //throw new Exception("We handled and wrapped your exception", arithExc);
    }
  }

  static void Mult(decimal x, decimal y)
  {
    decimal.Multiply(x, y);
  }
  static void Div(decimal x, decimal y)
  {
    decimal.Divide(x, y);
  }
}

إذا كنت uncomment على throw arithExc; خط ، خرجك هو:

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 44
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

من المؤكد أنك فقدت معلومات حول مكان حدوث هذا الاستثناء. إذا كنت تستخدم throw; بدلاً من ذلك throw; هذا ما تحصل عليه:

Handling a DivideByZeroException.
Your stack trace:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 46
   at Program.Main() in c:\somepath\Program.cs:line 9

No inner exception.

هذا أفضل بكثير ، لأنك الآن ترى أنه كان أسلوب Program.Div الذي تسبب في حدوث مشكلات. ولكن ما زال من الصعب معرفة ما إذا كانت هذه المشكلة تأتي من السطر 35 أو السطر 37 في كتلة try .

إذا كنت تستخدم البديل الثالث ، الالتفاف في استثناء خارجي ، فإنك لا تفقد أي معلومات:

Handling a DivideByZeroException.
Your stack trace:
   at Program.ThrowTest() in c:\somepath\Program.cs:line 48
   at Program.Main() in c:\somepath\Program.cs:line 9

Stack trace of your inner exception:
   at System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
   at System.Decimal.Divide(Decimal d1, Decimal d2)
   at Program.Div(Decimal x, Decimal y) in c:\somepath\Program.cs:line 58
   at Program.ThrowTest() in c:\somepath\Program.cs:line 35

على وجه الخصوص يمكنك أن ترى أنه السطر 35 الذي يؤدي إلى المشكلة. ومع ذلك ، يتطلب هذا من الأشخاص البحث في InnerException ، ويشعر بشكل غير مباشر إلى حد ما باستخدام الاستثناءات الداخلية في الحالات البسيطة.

في منشور المدونة هذه ، تحتفظ برقم السطر (سطر كتلة المحاولة) عن طريق الاتصال (من خلال الانعكاس) بطريقة التداخل InternalPreserveStackTrace() على كائن Exception . ولكن ليس من اللطيف استخدام انعكاس كهذا (قد يقوم .NET Framework بتغيير أعضائها internal يومًا ما دون تحذير).


MSDN لتقف على :

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


دعونا نفهم الفرق بين رمي ورمي السابقين. سمعت أنه في العديد من المقابلات .net يتم طرح هذا السؤال الشائع.

فقط لإعطاء نظرة عامة على هذين المصطلحين ، تستخدم كلا من رمي ورمي السابقين لفهم أين حدث الاستثناء. يرمي السابق Exrow reckites في تتبع مكدس الاستثناء بغض النظر عن مكان الإلقاء الفعلي.

دعونا نفهم مع مثال.

دعونا نفهم أولا رمي.

static void Main(string[] args) {
        try {
            M1();
        } catch (Exception ex) {
            Console.WriteLine(" -----------------Stack Trace Hierarchy -----------------");
            Console.WriteLine(ex.StackTrace.ToString());
            Console.WriteLine(" ---------------- Method Name / Target Site -------------- ");
            Console.WriteLine(ex.TargetSite.ToString());
        }
        Console.ReadKey();
}

static void M1() {
        try {
            M2();
        } catch (Exception ex) {
            throw;
        };
}

static void M2() {
    throw new DivideByZeroException();
}

خرج ما ورد أعلاه أدناه.

يعرض التسلسل الهرمي الكامل واسم الطريقة حيث تم بالفعل طرح الاستثناء .. هو M2 -> M2. جنبا إلى جنب مع أرقام الخطوط

ثانيا .. لنفهم من خلال رمي السابقين. مجرد استبدال رمي بالرمي السابقين في كتلة الصيد طريقة M2. على النحو التالي.

الناتج من كود رمي السابقين هو على النحو التالي ..

تستطيع أن ترى الفرق في الإخراج .. رمي السابقين يتجاهل فقط كل التسلسل الهرمي السابق ويعيد تراكم التتبع مع الخط / الطريقة حيث يتم كتابة الرمية السابقة.


لا ، سيؤدي هذا الاستثناء لجعل تتبع مكدس مختلفة. سيؤدي استخدام عملية throw بدون أي كائن استثناء في معالج catch إلى ترك تتبع المكدس بدون تغيير.

قد ترغب في إرجاع منطقي من HandleException ما إذا كان يجب إعادة الاستثناء أم لا.


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

إذا كنت رمي ​​، الاستثناء فقط ينتقل إلى الأسفل وستحصل على تتبع المكدس الكامل.


(نشرت في وقت سابق ، وقد صححنيMarc Gravell)

فيما يلي توضيح للفرق:

        static void Main(string[] args) {
        try {
            ThrowException1(); // line 19
        } catch (Exception x) {
            Console.WriteLine("Exception 1:");
            Console.WriteLine(x.StackTrace);
        }
        try {
            ThrowException2(); // line 25
        } catch (Exception x) {
            Console.WriteLine("Exception 2:");
            Console.WriteLine(x.StackTrace);
        }
    }

    private static void ThrowException1() {
        try {
            DivByZero(); // line 34
        } catch {
            throw; // line 36
        }
    }
    private static void ThrowException2() {
        try {
            DivByZero(); // line 41
        } catch (Exception ex) {
            throw ex; // line 43
        }
    }

    private static void DivByZero() {
        int x = 0;
        int y = 1 / x; // line 49
    }

وهنا الناتج:

Exception 1:
   at UnitTester.Program.DivByZero() in <snip>\Dev\UnitTester\Program.cs:line 49
   at UnitTester.Program.ThrowException1() in <snip>\Dev\UnitTester\Program.cs:line 36
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 19

Exception 2:
   at UnitTester.Program.ThrowException2() in <snip>\Dev\UnitTester\Program.cs:line 43
   at UnitTester.Program.TestExceptions() in <snip>\Dev\UnitTester\Program.cs:line 25

يمكنك مشاهدة ذلك في "استثناء 1" ، يعود تتبع المكدس إلى أسلوب DivByZero() ، بينما في استثناء 2 فإنه لا.

مع ملاحظة أن رقم السطر الموضح في ThrowException1() و ThrowException2() هو رقم سطر عبارة throw ، وليس رقم السطر الخاص بالمكالمة إلى DivByZero() ، التي قد يكون من المنطقي الآن أن أفكر في الأمر قليلا...

الإخراج في وضع الإصدار

استثناء 1:

at ConsoleAppBasics.Program.ThrowException1()
at ConsoleAppBasics.Program.Main(String[] args)

استثناء 2:

at ConsoleAppBasics.Program.ThrowException2()
at ConsoleAppBasics.Program.Main(String[] args)

هل يحافظ على stackTrace الأصلي في وضع التصحيح فقط؟





exception-handling