البرمجة الوظيفية في C#: تقنيات وأنماط متقدمة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

البرمجة الوظيفية في C#: من التركيب إلى البنية

\n\n

لقد تجاوزت لغة C# الحديثة حدود كونها لغة كائنية التوجه بحتة. بدءًا من الإصدار 3.0 (LINQ) وصولاً إلى أحدث إصدارات .NET، تستوعب اللغة بنشاط نماذج البرمجة الوظيفية (FP). إذا كنت قد أتقنت دوال lambda و LINQ ولكنك تريد التعمق أكثر، فهذه المقالة لك. سنقوم بتحليل المفاهيم المتقدمة للبرمجة الوظيفية التي ستجعل كود C# الخاص بك أكثر قابلية للتنبؤ والاختبار والإيجاز.

\n\n

1. الدوال النقية والتعامل مع التأثيرات الجانبية

\n\n

الدالة النقية هي دالة:

\n
    \n
  • تعيد دائمًا نفس النتيجة لنفس الوسائط (الشفافية المرجعية).
  • \n
  • ليس لها تأثيرات جانبية (لا تغير الحالة العامة، لا تكتب في ملف، لا ترسل طلبات HTTP).
  • \n
\n\n

في C#، المثال المثالي للدالة النقية هو العمليات الرياضية. المشكلة هي أن التطبيق الحقيقي لا يمكن أن يتكون فقط من دوال نقية: عاجلاً أم آجلاً، ستحتاج إلى حفظ البيانات أو إرسال بريد إلكتروني. الحل هو فصل المنطق عن التأثيرات.

\n\n

تقنية متقدمة: استخدم الـ functors والمونادات لتغليف التأثيرات. لنلق نظرة على مثال بسيط مع Maybe<T> (مشابه لـ nullable لكن وظيفي):

\n\n
public class Maybe<T>\n{\n    private readonly T _value;\n    public bool HasValue { get; }\n    \n    private Maybe(T value, bool hasValue) => (_value, HasValue) = (value, hasValue);\n    \n    public static Maybe<T> Some(T value) => new Maybe<T>(value, true);\n    public static Maybe<T> None() => new Maybe<T>(default, false);\n    \n    // Functor: Select (Map)\n    public Maybe<TResult> Select<TResult>(Func<T, TResult> selector) =>\n        HasValue ? Maybe<TResult>.Some(selector(_value)) : Maybe<TResult>.None();\n    \n    // Monad: SelectMany (FlatMap/Bind)\n    public Maybe<TResult> SelectMany<TResult>(Func<T, Maybe<TResult>> binder) =>\n        HasValue ? binder(_value) : Maybe<TResult>.None();\n}\n\n// الاستخدام\nMaybe<int> result = Maybe<int>.Some(10)\n    .Select(x => x * 2)\n    .SelectMany(x => x > 15 ? Maybe<int>.Some(x) : Maybe<int>.None());
\n\n

هذا الكود لا يولد استثناءات ولا يتحقق من null يدويًا - كل منطق معالجة غياب القيمة مغلف داخل الموناد.

\n\n

2. الكاري والتطبيق الجزئي

\n\n

في البرمجة الوظيفية التقليدية، كل دالة تأخذ وسيطة واحدة. الكاري هو تحويل دالة ذات عدة معاملات إلى سلسلة من الدوال المتداخلة، كل منها تأخذ وسيطة واحدة. التطبيق الجزئي هو تثبيت جزء من الوسائط.

\n\n

في C#، لا يوجد كاري مدمج، لكن من السهل تنفيذه عبر الإغلاقات:

\n\n
// دالة عادية\nFunc<int, int, int, int> add = (a, b, c) => a + b + c;\n\n// النسخة المكاريّة\nFunc<int, Func<int, Func<int, int>>> curriedAdd = a => b => c => a + b + c;\n\n// تطبيق جزئي: تثبيت الوسيطة الأولى\nFunc<int, Func<int, int>> add5 = curriedAdd(5);\n\n// الاستخدام\nint sum = add5(10)(20); // 35
\n\n

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

\n\n
public class ReportService\n{\n    public Func<string, string, string> FormatReport => \n        (template, data) => $"[{DateTime.Now}] {template}: {data}";\n}\n\n// الاستخدام مع التطبيق الجزئي\nvar service = new ReportService();\nvar formatWithTemplate = service.FormatReport("Error"); // تثبيت القالب\nstring errorMessage = formatWithTemplate("Connection failed");                

المدونات

توصيات الكتب