التقاط الاستثناءات Catching
يُمكن للشيفرة -بل يَنبغي لها- أن تُبلِّغ عن استثناءات Exceptions في بعض الظروف الاستثنائية. مثلًا:
- مُحاولة قراءة مَجْرى مقروء
- محاولة قراءة ملف بدون تَوفُّر صلاحيات الولوج
- محاولة القيام بعملية غير صالحة مثل القسمة على الصفر
- حُدوث انتهاء مهلة timeout أثناء تحميل ملف عبر الشبكة العنكبوتية
ينبغي لمُستَدعِي ما 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
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.