Menu
flag Ar iconالعربيةdown icon

try catch في C#: معالجة الاستثناءات بـ catch وwhen وfinally

try وcatch وfinally هي طريقة C# في معالجة الاستثناءات. تعلّم كيف ينتقل الاستثناء صعودًا في مكدّس الاستدعاءات، وكيف تلتقط أنواع استثناءات محددة بالترتيب الصحيح، وتصفّي بـ when، وتنظّف في finally، وتعيد الرمي بـ throw; دون فقدان أثر المكدّس، وأي الاستثناءات لا تلتقطها.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

حين يحدث خطأ وقت التشغيل (ملف مفقود، نص ليس رقمًا، مفتاح غير موجود في قاموس) ترمي .NET استثناءً: كائنًا يصف الخطأ. ينتقل الاستثناء صعودًا في مكدّس الاستدعاءات حتى تعالجه كتلة catch. وإن لم يعالجه شيء يتوقف البرنامج ويطبع الخطأ.

try catch أساسية

غلّف الشيفرة التي قد تفشل في try، وعالج الفشل في catch:

المخرجات:

42 doubled is 84
'forty-two' is not a number
7 doubled is 14
Still running

حين ترمي int.Parse("forty-two") تُتخطّى Console.WriteLine التي بعدها في كتلة try، وتُنفَّذ كتلة catch (FormatException)، وتستمر الحلقة. يمكنك حذف المتغيّر (catch (FormatException)) حين لا تحتاج إلى كائن الاستثناء.

لهذه الحالة بالذات أداة أفضل: تعيد int.TryParse(input, out int n) القيمة false بدل رمي استثناء. الاستثناءات للحالات التي لا تتوقعها الشيفرة؛ أما المدخلات التي تكون غالبًا غير صالحة فمتوقعة، فافحصها بدل التقاطها.

كيف ينتقل الاستثناء

الاستثناء المرمي في عمق سلسلة استدعاءات يفكّ كل دالة حتى يجد معالجًا مطابقًا. الشيفرة التي بعد الرمي في كل من تلك الدوال لا تُنفَّذ.

المخرجات:

OrderTotal finished
5.00
Caught KeyNotFoundException in Main

لا catch في PriceOf وOrderTotal، فيمرّ الاستثناء عبرهما مباشرة إلى Main. ضع معالجًا في المستوى الذي يعرف ما يفعله إزاء الفشل، وهو غالبًا ليس حيث حدث.

التقاط استثناءات محددة، بالترتيب الصحيح

يعالج بند catch نوعه وكل نوع مشتق منه. وحين توجد عدة بنود يفوز أول واحد يطابق، فرتّبها من الأكثر تحديدًا إلى الأعم:

المخرجات:

10 / 2 = 5
Cannot divide by zero
Both values must be whole numbers
Unexpected: OverflowException

يرمي الاستدعاء الأخير OverflowException، التي لا يعالجها أي من البندين المحددين، فيعالجها البند العام catch (Exception e). ووضع catch (Exception) أولًا يجعل البنود التي بعده غير قابلة للوصول، ويرفض المترجم ذلك بالخطأ CS0160.

مرشّحات الاستثناء بـ when

أضافت C# 6 البند when: شرط يقرّر هل ينطبق بند catch. إن كان false يواصل وقت التشغيل البحث عن معالج آخر كأن البند غير موجود.

المخرجات:

Not found: show an empty page
Server error 503: retry later
Unhandled by Call: HTTP 401

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

finally: شيفرة تُنفَّذ دائمًا

تُنفَّذ كتلة finally حين يغادر التحكم try، سواء انتهت، أو عادت مبكرًا، أو رمت استثناءً. هناك مكان التنظيف. (تحفّظ واحد: إن لم يلتقط شيء في أي مكان الاستثناء، فقد تنتهي العملية دون تنفيذها.)

المخرجات:

Open connection
Close connection
finished
Open connection
Close connection
returned early
Open connection
Close connection
handled error

في كل حالة تُطبع "Close connection" قبل أن تصل نتيجة الدالة إلى Main: تُنفَّذ finally بعد حساب قيمة return لكن قبل أن تعود الدالة فعلًا. ويمكن أن تملك try كتلة finally دون أي catch، فتنظّف مع ترك الاستثناء يستمر إلى المستدعي.

للكائنات التي تنفّذ IDisposable (الملفات والتدفقات والاتصالات) تكتب العبارة using هذه try/finally عنك.

إعادة الرمي: throw; مقابل throw e;

أحيانًا تسجّل كتلة catch شيئًا أو تحفظه ثم تترك الاستثناء يستمر. طريقة إعادة الرمي تقرّر هل ينجو أثر المكدّس:

المخرجات:

throw;   trace mentions LoadConfig: True
throw e; trace mentions LoadConfig: False

تعامل throw e; الاستثناء كأنه رُمي حديثًا من كتلة catch، فتختفي من الأثر الإطارات التي تحتها، بما في ذلك الدالة التي حدث فيها الخطأ. أعد الرمي دائمًا بـ throw; مجردة. (السمة NoInlining موجودة فقط لأن JIT قد يدمج دالة بهذا الصغر في مستدعيها، فيخفيها من الأثرين.)

لإضافة سياق بدلًا من ذلك غلّف الاستثناء في واحد جديد ومرّر الأصلي كاستثناء داخلي: throw new ConfigException("Could not start the app", e);. يحتفظ الاستثناء الداخلي بأثر مكدّسه الخاص، وتطبع أدوات التسجيل السلسلة. وكتابة أنواع استثناءاتك الخاصة مشروحة في رمي الاستثناءات.

كائن Exception

كل استثناء مشتق من System.Exception. الأعضاء التي تستخدمها أكثر:

العضوما يحمله
Messageوصف مقروء للإنسان
GetType().Nameنوع الاستثناء، مثل FormatException
StackTraceسلسلة استدعاءات الدوال عند نقطة الرمي
InnerExceptionالاستثناء الذي سبّب هذا، أو null
ToString()النوع والرسالة والاستثناءات الداخلية وأثر المكدّس معًا

سجّل e.ToString() بدل e.Message حين تريد تشخيص فشل لاحقًا: الرسالة وحدها نادرًا ما تقول أين كانت المشكلة. ولا تعرض e.ToString() على المستخدمين النهائيين.

أنواع الاستثناءات الشائعة

الاستثناءالسبب النموذجي
NullReferenceExceptionاستدعاء عضو على مرجع null
ArgumentNullExceptionمُرّرت null إلى دالة حيث تحتاج إلى قيمة
ArgumentOutOfRangeExceptionوسيط أو فهرس قائمة خارج النطاق المسموح
IndexOutOfRangeExceptionفهرس مصفوفة خارج حدودها
FormatExceptionint.Parse وDateTime.Parse وأمثالهما على نص بصيغة خاطئة
InvalidCastExceptionتحويل صريح إلى نوع ليس هو نوع الكائن
InvalidOperationExceptionالكائن في حالة خاطئة للاستدعاء (تسلسل فارغ، مجموعة معدّلة)
KeyNotFoundExceptionقراءة مفتاح قاموس غائب بالمفهرس
DivideByZeroExceptionقسمة عدد صحيح أو decimal على صفر
OverflowExceptionرقم مقروء أو تحويل مفحوص أو نتيجة حساب مفحوص لا يتّسع لها النوع
FileNotFoundException، IOExceptionمشكلات نظام الملفات

تعني NullReferenceException وIndexOutOfRangeException وInvalidCastException دائمًا تقريبًا خطأً برمجيًا. أصلح الشيفرة بدل التقاطها.

عدم ابتلاع الاستثناءات

كتلة catch الفارغة تخفي كل خطأ، بما في ذلك التي لم تتوقعها:

try
{
    SaveOrder(order);
}
catch (Exception)
{
    // nothing: the order silently was not saved
}

يستمر البرنامج في العمل كأن الحفظ نجح، ويضيع السبب الحقيقي. إرشادات تبقي معالجة الاستثناءات صادقة:

  • لا تلتقط إلا ما تستطيع معالجته، في المستوى الذي يستطيع معالجته.
  • إن التقطت للتسجيل فأعد الرمي بـ throw; ما لم يكن البرنامج قادرًا فعلًا على المتابعة.
  • لا تلتقط Exception إلا عند الحافة الخارجية: Main، أو معالج طلب، أو حلقة عامل.
  • فضّل TryParse وTryGetValue وفحوص null على الاستثناءات للحالات المتوقعة. الرمي بطيء مقارنة بالفحص، بينما كتلة try التي لا ترمي شيئًا لا تكلّف شيئًا تقريبًا.

أخطاء شائعة

  • catch (Exception) أولًا. تصبح البنود اللاحقة غير قابلة للوصول (CS0160).
  • throw e; لإعادة الرمي. تمحو أثر المكدّس الأصلي؛ استخدم throw;.
  • كتل catch فارغة. تختفي الأخطاء؛ على الأقل سجّل وأعد الرمي.
  • استخدام الاستثناءات للتحكم في التدفق. تحقّق من المدخلات بـ TryParse بدل التقاط FormatException.
  • عرض e.Message لاستثناء من الإطار على المستخدمين. تختلف الصياغة بين إصدارات .NET وهي مكتوبة للمطوّرين.

الأسئلة الشائعة

كيف تعمل try catch في C#؟

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

هل تُنفَّذ finally دائمًا في C#؟

تقريبًا دائمًا: بعد انتهاء كتلة try بشكل طبيعي، وبعد أن تعالج catch استثناءً، وبعد return أو break داخل الكتلة، وحين يمرّ استثناء في طريقه إلى catch أعلى في مكدّس الاستدعاءات. ولا تُنفَّذ حين تنتهي العملية أولًا: عملية مقتولة، أو Environment.FailFast، أو StackOverflowException، وفي .NET Core وما بعده استثناء لا يلتقطه شيء، فينهي العملية قبل تنفيذ كتل finally.

كيف ألتقط عدة استثناءات في C#؟

اكتب عدة بنود catch، الأكثر تحديدًا أولًا: catch (FileNotFoundException) قبل catch (IOException) قبل catch (Exception). يرفض المترجم البند الذي لا يمكن الوصول إليه أبدًا لأن بندًا سابقًا يلتقط نوعه أصلًا. ولمعالجة نوعين غير مترابطين بالطريقة نفسها استخدم مرشّحًا: catch (Exception e) when (e is FormatException || e is OverflowException).

ما الفرق بين throw وthrow ex في C#؟

داخل catch تعيد throw; رمي الاستثناء الحالي بأثر مكدّسه الأصلي. أما throw ex; فترمي الكائن نفسه لكنها تعيد ضبط أثر المكدّس إلى السطر الحالي، فتختفي من الأثر الدالة التي حدث فيها الخطأ فعلًا. استخدم throw;، أو غلّفه: throw new MyException("context", ex);.

ما هو مرشّح الاستثناء في C#؟

بند when بعد catch (C# 6 وما بعده): catch (HttpRequestException e) when (e.Message.Contains("404")). لا تُنفَّذ كتلة catch إلا إن كان الشرط true؛ وإلا يظل الاستثناء يبحث عن معالج آخر كأن البند غير موجود، ولا يُفكّ مكدّسه.

هل ألتقط Exception في C#؟

فقط عند حواف البرنامج: أعلى Main، أو معالج طلب، أو حلقة في الخلفية، حيث المهمة تسجيل الخطأ والمتابعة أو الخروج بنظافة. أما في عمق الشيفرة فالتقط الأنواع المحددة التي تستطيع معالجتها فعلًا، ودع كل ما عداها ينتشر.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن