اذهب إلى المحتوى

الاستثناءات Exceptions في dot NET


رضوى العربي

التقاط الاستثناءات Catching

يُمكن للشيفرة -بل يَنبغي لها- أن تُبلِّغ عن استثناءات Exceptions في بعض الظروف الاستثنائية. مثلًا:

ينبغي لمُستَدعِي ما caller التقاط catch الاستثناءات المُحتملة والتعامل معها فقط عندما:

  • يستطيع إصلاح الظرف الاستثنائي أو التعافي منه بشكل مناسب.
  • إضافة معلومات عن السياق الذي حَدَثَ خلاله الاستثناء، قبل أن يَقوم بإعادة التبلِّيغ عنه مرة أُخْرى. (تُلْتقَط الاستثناءات المُعاد إلقائها عن طريق مُلتقِطي الاستثناءات بمُستوى أعلى بمَكْدَس الاستدعاءات call stack).

إذا أردت التقاط الاستثناءات المُلقاة من شيفرة معينة، اِستخدِم الصيغة التالية:

try 
{ 

} 
catch (ExceptionType) 
{ 

}‎

تُتضمَّن الشيفرة ذاتها داخل كتلة try، بينما تُتضمَّن الشيفرة المسئولة عن التقاط استثناء معين والتعامل معه بكتلة catch الخاصة به، كالمثال التالي:

Console.Write("Please enter a filename: ");
string filename = Console.ReadLine();
Stream fileStream;

try
{
    fileStream = File.Open(filename);
}
catch (FileNotFoundException)
{
    Console.WriteLine("File '{0}' could not be found.", filename);
}

لا يُعدّ عدم التقاط استثناء معين خطأً بالضرورة، إذا كنت تنوي التقاطه والتعامل معه بمُستوى أعلى بمَكْدَس الاستدعاءات call stack.

إعادة التبليغ Re-throwing

إذا أردت تَنفيذ أمر مُعين في حالة حدوث استثناء، ولكنك في نفس الوقت لا تستطيع إكمال تَّنفيذ الشيفرة بسبب هذا الاستثناء، فببساطة يُمكنك التقاط الاستثناء ثم إعادة التبليغ عنه بعد تَّنفيذ الأمر الذي تُريده، وبذلك سيلتقِطه مُلتقِط الاستثناءات التالي بحسب مَكْدَس الاستدعاءات call stack.

هناك عدة طرائق لفعل ذلك، بعضها جيد والآخر سيء:

في المثال التالي، سيُبلَّغ عن استثناء من نوع DivideByZeroException. تَلتقِط كتلة catch الاستثناء وتَستخدِم الكلمة المفتاحية throw -بمُفردها وبدون تحدّيد قيمة الاستثناء- لإعادة التبليغ عن الاستثناء الذي قد اِلتَقَطته للتو. تُعدّ هذه الطريقة سليمة حيث يتم فيها الاحتفاظ بتعقبات المكدس stack trace بصورة سليمة.

private static void AskTheUltimateQuestion()
{
    try
    {
        var x = 42;
        var y = x / (x - x); // will throw a DivideByZeroException
    }
    catch (DivideByZeroException)
    {
        Console.WriteLine("Dividing by zero would destroy the universe.");
        throw;
    }
}

static void Main()
{
    try
    {
        AskTheUltimateQuestion();
    }
    catch
    {
    }
}

كالمثال السابق، تَلتقِط كتلة catch الاستثناء، لكن هنا تُعِيد التبليغ عنه باستخدام throw ex. في الواقع، رغم شيوع هذه الطريقة فهي غير سليمة؛ لأنها تُغيّر من تعقبات المكدس stack trace، التي ستُشير الآن إلى السطر الذي أعاد التبليغ عن الاستثناء بدلًا من الإشارة إلى المكان الأصلي المُتسبب به، لذلك لا تَستخدِم هذه الطريقة.

private static void AskTheUltimateQuestion()
{
    try
    {
        var x = 42;
        var y = x / (x - x); // will throw a DivideByZeroException

        Console.WriteLine("The secret to life, the universe, and everything is {1}", y);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Something else horrible happened. The exception: " + ex.Message);

        throw ex;
    }
}

static void Main()
{
    try
    {
        AskTheUltimateQuestion();
    }
    catch
    {
    }
}

في الأمثلة السابقة، أُعيدّ التبليغ عن استثناءات من نفس النوع، أما إذا أردت أن تُغيّر نوع الاستثناء مع تَضمِين الاستثناء الأصلي بداخله، استخدِم التعبير throw new ExceptionType. في هذه الحالة، ستُشير تعقبات المكدس stack trace إلى السطر المُتسبب بالاستثناء الخارجي. ولكن يُمكنك بسهولة استخدام الخاصية InnerException للولوج إلى تعقبات المكدس stack trace الخاصة بالاستثناء الداخلي والتي ستُشير إلى السطر الأصلي المُتسبب به. كالمثال التالي:

private static void AskTheUltimateQuestion()
{
    try
    {
        var x = 42;
        Console.WriteLine("The secret to life, the universe, and everything is {1}", y);
    }
    catch (FormatException ex)
    {
        throw new InvalidOperationException("Watch your format string indexes.", ex);
    }
}

static void Main()
{
    try
    {
        AskTheUltimateQuestion();
    }
    catch
    {
    }
}

ترشيح الاستثناءات

تُرَشَح الاستثناءات بناءً على النوع. مثلًا في المثال التالي، إذا بُلِّغ عن استثناء ما فسيتم تَّنفيذ أول كتلة catch مُتوافقة مع نوع الاستثناء. يُمكنك إضافة كتلة catch بالنهاية للتعامل مع النوع Exception، بحيث يُلجَئ إليها إذا لم تَتوافق معه كتلة أُخْرى أكثر تحدّيدًا:

private static void AskTheUltimateQuestion()
{
    try
    {
        var x = 42;
        var y = x / (x - x); 
        Console.WriteLine("The secret to life, the universe, and everything is {1}", y);
    }
    catch (DivideByZeroException)
    {
    }
    catch (FormatException ex)
    {
    }
    catch (Exception ex)
    {
    }
}

static void Main()
{
    try
    {
        AskTheUltimateQuestion();
    }
    catch
    {
    }
}

لا تُرَشَح الاستثناءات فقط بناءً على النوع، فبدءًا من الإصدار 6 للغة c#‎، يُرشِح العَامِل when الاستثناءات بناءً على خاصياتها كذلك. كالتالي:

try
{
    // ...
}
catch (Exception e) when (e.InnerException != null) // Any condition can go in here.
{
    // ...
}

يُشبه ذلك استخدام جمل شرطية بداخل كتلة catch، لكن دون التسبب بأي تَغيِير بالمَكْدَس في حالة عدم تَحقُّق الشرط.

إعادة التبليغ عن استثناء بواسطة تابع آخر

قد تَحتاج أحيانًا إلى التقاط استثناء وإعادة التبليغ عنه بواسطة تابع أو خيط thread آخر، وفي ذات الوقت تريد أن تحتفظ برصة التتبع الأصلية. يُستخدَم التابع ExceptionDispatchInfo.Capture للقيام بذلك كالتالي:

using System.Runtime.ExceptionServices;
void Main()
{
    ExceptionDispatchInfo capturedException = null;
    try
    {
        throw new Exception();
    }
    catch (Exception ex)
    {
        capturedException = ExceptionDispatchInfo.Capture(ex);
    }

    Foo(capturedException);
}
void Foo(ExceptionDispatchInfo exceptionDispatchInfo)
{
    // Do stuff
    if (capturedException != null)
    {
        // Exception stack trace will show it was thrown from Main() and not from Foo()
        exceptionDispatchInfo.Throw();
    }
}

استخدام كتلة finally

تُنفَّذ كتلة finally {...}‎ دائما بغض النظر عما إذا كان قد تم التبليغ عن استثناء أم لا أثناء تَّنفيذ الشيفرة الموجودة بكتلة try {...}‎ -يُستثني من ذلك حدوث استثناء من نوع StackOverflowException أو إذا اُستدْعِيَ التابع Environment.FailFast()‎-، لذلك هي ملائمة لتحرير أيّ موارد resources حُجزت بكتلة try بطريقة آمنة.

Console.Write("Please enter a filename: ");
string filename = Console.ReadLine();
Stream fileStream = null;
try
{
    fileStream = File.Open(filename);
}
catch (FileNotFoundException)
{
    Console.WriteLine("File '{0}' could not be found.", filename);
}
finally
{
    if (fileStream != null)
    {
        fileStream.Dispose();
    }
}

ترجمة -وبتصرف- للفصل Exceptions والفصل ForEach من كتاب ‎.NET Framework Notes for Professionals


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...