معالجة الأخطاء المتقدمة في C#: من try/catch الأساسي إلى الأنماط الصناعية
\n\nمعالجة الأخطاء ليست مجرد حظر للاستثناءات، بل هي فن تصميم أنظمة موثوقة. في C# (.NET)، غالبًا ما يقتصر المطورون على استخدام try/catch، لكن المنصة تقدم آليات أكثر قوة. في هذه المقالة، سنستعرض التقنيات المتقدمة: من مرشحات الاستثناءات إلى أنماط إعادة المحاولة ونقل سياق الخطأ.
1. مرشحات الاستثناءات: الدقة بدلاً من الحشو
\n\nمرشحات الاستثناءات (Exception Filters)، التي ظهرت في C# 6.0، تسمح بتحديد شرط للدخول إلى كتلة catch دون إعادة رمي الاستثناء. هذا أنظف بكثير من النهج التقليدي باستخدام if داخل الكتلة.
try\n{\n // كود قد يرمي استثناءً\n var result = ProcessData(input);\n}\ncatch (CustomException ex) when (ex.ErrorCode == 1001)\n{\n // معالجة الأخطاء ذات الرمز 1001 فقط\n Log.Error($"خطأ حرج: {ex.Message}");\n throw; // إعادة الرمي إذا لزم الأمر\n}\ncatch (CustomException ex) when (ex.IsRecoverable)\n{\n // خطأ قابل للاسترداد - نسجله ونواصل\n Log.Warning($"خطأ قابل للاسترداد: {ex.Message}");\n RetryOperation();\n}\n\nالمزايا:
\n- \n
- لا حاجة لإعادة
throwللتصفية. \n - تحسين قابلية القراءة والأداء (لا يتلف مكدس الاستدعاءات). \n
- يمكن دمج عدة كتل
catchبشروط مختلفة. \n
2. نمط "إعادة المحاولة" (Retry) وPolly
\n\nليست كل الأخطاء قاتلة. للأعطال المؤقتة (مشاكل الشبكة، أقفال قاعدة البيانات)، استخدم نمط إعادة المحاولة. الأدوات المدمجة في C# محدودة، لذا يُوصى باستخدام مكتبة Polly.
\n\nusing Polly;\nusing Polly.Retry;\n\n// تحديد سياسة إعادة المحاولة\nAsyncRetryPolicy retryPolicy = Policy\n .Handle<HttpRequestException>() // معالجة أخطاء HTTP فقط\n .WaitAndRetryAsync(\n 3, // عدد المحاولات\n retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), // تأخير أسي\n onRetry: (exception, timeSpan, retryCount, context) =>\n {\n Log.Warning($"المحاولة {retryCount} فشلت. إعادة المحاولة بعد {timeSpan.Seconds} ثانية.");\n });\n\n// تطبيق السياسة\nawait retryPolicy.ExecuteAsync(async () =>\n{\n var response = await httpClient.GetAsync("https://api.example.com/data");\n response.EnsureSuccessStatusCode();\n});\n\nهام: لا تستخدم إعادة محاولة لا نهائية. حدد دائمًا أقصى عدد من المحاولات واستخدم Circuit Breaker لحماية النظام من الأعطال المتتالية.
\n\n3. النقل الصحيح للاستثناءات: throw مقابل throw ex
\n\nأحد الأخطاء الأكثر شيوعًا هو فقدان مكدس الاستدعاءات. الفرق بين throw و throw ex حاسم لتصحيح الأخطاء.
try\n{\n // ...\n}\ncatch (Exception ex)\n{\n // خطأ: نفقد المكدس الأصلي\n // throw ex; \n\n // صحيح: نحتفظ بالمكدس\n throw;\n\n // أو: نضيف سياقًا دون فقدان الأصل\n // throw new ApplicationException("خطأ في الوحدة X", ex);\n}\n\nإذا كنت بحاجة لإضافة سياق، استخدم ExceptionDispatchInfo (من مساحة System.Runtime.ExceptionServices) للحفاظ الدقيق على المكدس عند إعادة الرمي بين الخيوط.
ExceptionDispatchInfo.Capture(exception).Throw(); // يحتفظ بالمكدس الأصلي\n\n4. الاستثناءات المخصصة وسياق الخطأ
\n\nالاستثناءات القياسية غالبًا لا تحمل سياقًا تجاريًا. أنشئ فئات الاستثناءات الخاصة بك مع خصائص إضافية.
\n\npublic class PaymentProcessingException : Exception\n{\n public int OrderId { get; }\n public dec