فحص كيفية إنشاء الاستثناءات وطرحها في C#
- 16 دقائق
يوفر .NET تسلسلا هرميا لفئات الاستثناء التي يتم اشتقاقها System.Exception من الفئة الأساسية. يمكن لتطبيقات C# إنشاء استثناءات من أي نوع استثناء وطرحها. يمكن للمطورين أيضا تخصيص كائنات الاستثناء بمعلومات خاصة بالتطبيق عن طريق تعيين قيم الخصائص.
إشعار
تركز هذه الوحدة على إنشاء الاستثناءات وطرحها وتخصيص كائنات الاستثناءات. إنشاء فئات استثناء مخصصة خارج نطاق هذه الوحدة النمطية.
إنشاء كائن استثناء
يعد إنشاء استثناءات وطرحها من داخل التعليمات البرمجية الخاصة بك جانبا مهما من جوانب برمجة C#. تساعدك القدرة على إنشاء استثناء استجابة لحالة أو مشكلة أو خطأ معين على ضمان استقرار التطبيق الخاص بك.
يعتمد نوع الاستثناء الذي تقوم بإنشائه على مشكلة الترميز، ويجب أن يتطابق مع الغرض المقصود من الاستثناء قدر الإمكان.
على سبيل المثال، افترض أنك تقوم بإنشاء أسلوب يسمى GraphData يقوم بتحليل البيانات. يتلقى الأسلوب صفيف بيانات كمعلمة إدخال. يتوقع الأسلوب أن تكون بيانات الإدخال في نطاق معين. إذا كان الأسلوب يتلقى بيانات خارج النطاق المتوقع، فإنه ينشئ ويطرح استثناء من النوع ArgumentException. سيتم التعامل مع الاستثناء في مكان ما أسفل مكدس الاستدعاءات بواسطة التعليمات البرمجية المسؤولة عن توفير البيانات.
فيما يلي بعض أنواع الاستثناءات الشائعة التي قد تستخدمها عند إنشاء استثناء:
-
ArgumentExceptionأوArgumentNullException: استخدم أنواع الاستثناءات هذه عند استدعاء أسلوب أو منشئ بقيمة وسيطة غير صحيحة أو مرجع فارغ. -
InvalidOperationException: استخدم نوع الاستثناء هذا عندما لا تدعم شروط التشغيل لأسلوب الإكمال الناجح لاستدعاء أسلوب معين. -
NotSupportedException: استخدم نوع الاستثناء هذا عندما تكون عملية أو ميزة غير معتمدة. -
IOException: استخدم نوع الاستثناء هذا عند فشل عملية إدخال/إخراج. -
FormatException: استخدم نوع الاستثناء هذا عندما يكون تنسيق سلسلة أو بيانات غير صحيح.
new يتم استخدام الكلمة الأساسية لإنشاء مثيل استثناء. على سبيل المثال، يمكنك إنشاء مثيل لنوع الاستثناء ArgumentException كما يلي:
ArgumentException invalidArgumentException = new ArgumentException();
تكوين الاستثناءات المخصصة وطرحها
تتضمن عملية طرح كائن استثناء إنشاء مثيل لفئة مشتقة من الاستثناء، وتكوين خصائص الاستثناء اختياريا، ثم طرح الكائن باستخدام throw الكلمة الأساسية .
غالبا ما يكون من المفيد تخصيص استثناء بمعلومات سياقية قبل طرحه. يمكنك توفير معلومات خاصة بالتطبيق داخل كائن استثناء عن طريق تكوين خصائصه. على سبيل المثال، تنشئ التعليمات البرمجية التالية كائن استثناء يسمى invalidArgumentException بخاصية مخصصة Message ، ثم تطرح الاستثناء:
ArgumentException invalidArgumentException = new ArgumentException("ArgumentException: The 'GraphData' method received data outside the expected range.");
throw invalidArgumentException;
إشعار
Message خاصية الاستثناء للقراءة فقط. لذلك، يجب تعيين خاصية مخصصة Message عند إنشاء مثيل للكائن.
عند تخصيص كائن استثناء، من المهم توفير رسائل خطأ واضحة تصف المشكلة وكيفية حلها. يمكنك أيضا تضمين معلومات إضافية مثل تتبعات المكدس ورموز الخطأ لمساعدة المستخدمين على تصحيح المشكلة.
يمكن أيضا إنشاء كائن استثناء مباشرة داخل عبارة throw . على سبيل المثال:
throw new FormatException("FormatException: Calculations in process XYZ have been cancelled due to invalid data format.");
تتضمن بعض الاعتبارات التي يجب وضعها في الاعتبار عند طرح استثناء ما يلي:
-
Messageيجب أن توضح الخاصية سبب الاستثناء. ومع ذلك، لا ينبغي وضع المعلومات الحساسة أو التي تمثل مشكلة أمنية في نص الرسالة. -
StackTraceغالبا ما تستخدم الخاصية لتعقب أصل الاستثناء. تحتوي خاصية السلسلة هذه على اسم الأساليب الموجودة في مكدس الاستدعاءات الحالي، بالإضافة إلى اسم الملف ورقم السطر في كل أسلوب مقترن بالاستثناء.StackTraceيتم إنشاء كائن تلقائيا بواسطة وقت تشغيل اللغة الشائعة (CLR) من نقطة العبارةthrow. يجب طرح الاستثناءات من النقطة التي يجب أن يبدأ فيها تتبع المكدس.
متى يتم طرح استثناء
يجب أن تطرح الأساليب استثناء كلما لم تتمكن من إكمال الغرض المقصود منها. يجب أن يستند الاستثناء الذي تم طرحه إلى الاستثناء الأكثر تحديدا المتوفر الذي يناسب شروط الخطأ.
ضع في اعتبارك سيناريو يعمل فيه مطور على تطبيق يقوم بتنفيذ عملية تجارية. تعتمد عملية العمل على إدخال المستخدم. إذا لم يتطابق الإدخال مع نوع البيانات المتوقع، فإن الأسلوب الذي ينفذ عملية العمل ينشئ ويطرح استثناء. يمكن تكوين كائن الاستثناء بمعلومات خاصة بالتطبيق في قيم الخاصية. يوضح نموذج التعليمات البرمجية التالي السيناريو:
string[][] userEnteredValues = new string[][]
{
new string[] { "1", "two", "3"},
new string[] { "0", "1", "2"}
};
foreach (string[] userEntries in userEnteredValues)
{
try
{
BusinessProcess1(userEntries);
}
catch (Exception ex)
{
if (ex.StackTrace.Contains("BusinessProcess1") && (ex is FormatException))
{
Console.WriteLine(ex.Message);
}
}
}
static void BusinessProcess1(string[] userEntries)
{
int valueEntered;
foreach (string userValue in userEntries)
{
try
{
valueEntered = int.Parse(userValue);
// completes required calculations based on userValue
// ...
}
catch (FormatException)
{
FormatException invalidFormatException = new FormatException("FormatException: User input values in 'BusinessProcess1' must be valid integers");
throw invalidFormatException;
}
}
}
في نموذج التعليمات البرمجية هذا، تستدعي BusinessProcess1 عبارات المستوى الأعلى الأسلوب ، تمرر في صفيف سلسلة يحتوي على قيم أدخلها المستخدم.
BusinessProcess1 يتوقع الأسلوب قيم إدخال المستخدم التي يمكن تحويلها إلى عدد صحيح. عندما يواجه الأسلوب بيانات بتنسيق غير صالح، فإنه ينشئ مثيلا لنوع الاستثناء FormatException باستخدام خاصية مخصصة Message . ثم يطرح الأسلوب الاستثناء. يتم التقاط الاستثناء في عبارات المستوى الأعلى ككائن يسمى ex.
ex يتم فحص خصائص الكائن قبل عرض رسالة الاستثناء للمستخدم. أولا، تفحص التعليمات البرمجية الخاصية StackTrace لمعرفة ما إذا كانت تحتوي على "BusinessProcess1". ثانيا، يتم التحقق من أن كائن ex الاستثناء من النوع FormatException.
إعادة طرح الاستثناءات
بالإضافة إلى طرح استثناء جديد، throw يمكن استخدام إعادة طرح استثناء من داخل كتلة التعليمات البرمجية catch . في هذه الحالة، throw لا تأخذ معامل استثناء.
catch (Exception ex)
{
// handle or partially handle the exception
// ...
// re-throw the original exception object for further handling down the call stack
throw;
}
عند إعادة طرح استثناء، يتم استخدام كائن الاستثناء الأصلي، لذلك لا تفقد أي معلومات حول الاستثناء. إذا كنت تريد إنشاء كائن استثناء جديد يلتف الاستثناء الأصلي، يمكنك تمرير الاستثناء الأصلي كوسيطة إلى الدالة الإنشائية لكائن استثناء جديد. على سبيل المثال:
catch (Exception ex)
{
// handle or partially handle the exception
// ...
// create a new exception object that wraps the original exception
throw new ApplicationException("An error occurred", ex);
}
بالنسبة لسيناريو تطبيق "BusinessProcess1"، ضع في اعتبارك التحديثات التالية:
-
BusinessProcess1تم تحديث الأسلوب لتضمين تفاصيل إضافية.BusinessProcess1تواجه الآن مشكلتين ويجب أن تنشئ استثناءات لكل مشكلة. - تم تحديث عبارات المستوى الأعلى. تستدعي
OperatingProcedure1عبارات المستوى الأعلى الآن الأسلوب .OperatingProcedure1استدعاءاتBusinessProcess1داخل كتلة التعليمات البرمجيةtry. -
OperatingProcedure1الأسلوب قادر على معالجة أحد أنواع الاستثناءات ومعالجة الآخر جزئيا. بمجرد معالجة الاستثناء الذي تمت معالجته جزئيا،OperatingProcedure1يجب إعادة طرح الاستثناء الأصلي.
يوضح نموذج التعليمات البرمجية التالي السيناريو المحدث:
try
{
OperatingProcedure1();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Exiting application.");
}
static void OperatingProcedure1()
{
string[][] userEnteredValues = new string[][]
{
new string[] { "1", "two", "3"},
new string[] { "0", "1", "2"}
};
foreach(string[] userEntries in userEnteredValues)
{
try
{
BusinessProcess1(userEntries);
}
catch (Exception ex)
{
if (ex.StackTrace.Contains("BusinessProcess1"))
{
if (ex is FormatException)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Corrective action taken in OperatingProcedure1");
}
else if (ex is DivideByZeroException)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Partial correction in OperatingProcedure1 - further action required");
// re-throw the original exception
throw;
}
else
{
// create a new exception object that wraps the original exception
throw new ApplicationException("An error occurred - ", ex);
}
}
}
}
}
static void BusinessProcess1(string[] userEntries)
{
int valueEntered;
foreach (string userValue in userEntries)
{
try
{
valueEntered = int.Parse(userValue);
checked
{
int calculatedValue = 4 / valueEntered;
}
}
catch (FormatException)
{
FormatException invalidFormatException = new FormatException("FormatException: User input values in 'BusinessProcess1' must be valid integers");
throw invalidFormatException;
}
catch (DivideByZeroException)
{
DivideByZeroException unexpectedDivideByZeroException = new DivideByZeroException("DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero");
throw unexpectedDivideByZeroException;
}
}
}
ينتج عن نموذج التعليمات البرمجية المحدث الإخراج التالي:
FormatException: User input values in 'BusinessProcess1' must be valid integers
Corrective action taken in OperatingProcedure1
DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero
Partial correction in OperatingProcedure1 - further action required
DivideByZeroException: Calculation in 'BusinessProcess1' encountered an unexpected divide by zero
Exiting application.
أشياء يجب تجنبها عند طرح استثناءات
تحدد القائمة التالية الممارسات التي يجب تجنبها عند طرح الاستثناءات:
- لا تستخدم الاستثناءات لتغيير تدفق البرنامج كجزء من التنفيذ العادي. استخدم الاستثناءات للإبلاغ عن حالات الخطأ ومعالجتها.
- لا يجب إرجاع الاستثناءات كقيمة إرجاع أو معلمة بدلا من طرحها.
- لا تقم بطرح
System.ExceptionأوSystem.SystemExceptionSystem.NullReferenceExceptionأو أوSystem.IndexOutOfRangeExceptionعن قصد من التعليمات البرمجية المصدر الخاصة بك. - لا تقم بإنشاء استثناءات يمكن طرحها في وضع تصحيح الأخطاء ولكن ليس وضع الإصدار. لتحديد أخطاء وقت التشغيل أثناء مرحلة التطوير، استخدم
Debug.Assertبدلا من ذلك.
إشعار
الأسلوب Debug.Assert هو أداة لاصطياد الأخطاء المنطقية أثناء التطوير. بشكل افتراضي، Debug.Assert يعمل الأسلوب فقط في بنيات تتبع الأخطاء. يمكنك استخدام Debug.Assert في جلسات تصحيح الأخطاء للتحقق من وجود شرط يجب ألا يحدث أبدا. يأخذ الأسلوب معلمتين: شرط منطقي للتحقق، ورسالة سلسلة اختيارية لعرضها إذا كان الشرط هو false.
Debug.Assert يجب عدم استخدام بدلا من طرح استثناء، وهو طريقة للتعامل مع المواقف الاستثنائية أثناء التنفيذ العادي للتعليمات البرمجية الخاصة بك. يجب استخدام Debug.Assert لالتقاط الأخطاء التي يجب ألا تحدث أبدا، واستخدام الاستثناءات لمعالجة الأخطاء التي قد تحدث أثناء التنفيذ العادي للبرنامج.
خلاصة
فيما يلي بعض الأشياء المهمة التي يجب تذكرها من هذه الوحدة:
- عند إنشاء استثناء وطرحه، يجب أن يتطابق نوع الاستثناء مع الغرض المقصود من الاستثناء قدر الإمكان.
- للاستثناء
throw، يمكنك إنشاء مثيل لفئة مشتقة من الاستثناء، وتكوين خصائصها، ثم استخدامthrowالكلمة الأساسية . - عند إنشاء كائن استثناء، من المهم توفير رسائل خطأ واضحة ومعلومات إضافية لمساعدة المستخدمين على تصحيح المشكلة.