مقدمة: عندما يقول الكود «لا أعرف»
\nتخيل موقفًا: أنت تكتب مكتبة تسلسل (serialization) بلغة C#. لديك دالة Serialize<T>(T obj) يمكنها تحويل أي كائن إلى سلسلة نصية. كل شيء رائع طالما تستدعيها بنوع محدد: Serialize<User>(user). لكن ماذا تفعل إذا أصبح نوع الكائن معروفًا فقط أثناء تنفيذ البرنامج؟
هذه مشكلة كلاسيكية يطرحها آلاف المطورين على Stack Overflow: «كيف أستدعي دالة عامة (generic method) باستخدام متغير Type؟». للوهلة الأولى، يبدو الحل بسيطًا، لكن C# لغة شديدة التحديد في الأنواع (strongly-typed)، ولا يمكنك ببساطة وضع متغير نوع داخل الأقواس الزاوية.
\n\nفي هذا الدليل التعليمي، سنشرح كيفية استدعاء الدوال العامة بشكل صحيح عبر الانعكاس (Reflection) في C# باستخدام MakeGenericMethod. ستتعلم ليس فقط الصياغة (syntax)، ولكن أيضًا متى يكون ذلك ضروريًا حقًا، ومتى يكون من الأفضل البحث عن بدائل.
لماذا لا يمكنك ببساطة كتابة Method<type>()؟
\nدعنا نبدد الأوهام فورًا. الكود التالي لن يتم تجميعه:
\nType runtimeType = someObject.GetType();\n// خطأ في التجميع: "النوع 'runtimeType' غير صالح"\nSomeMethod<runtimeType>();\n\n\nلماذا؟ لأن المعاملات العامة (generic parameters) في C# هي بيانات وصفية (metadata) لوقت التجميع. يجب أن يعرف المترجم (compiler) بالضبط النوع الذي يعمل معه لتوليد الكود. المتغير Type هو كائن يعيش فقط في وقت التشغيل (runtime). بالنسبة للمترجم، هو مجرد متغير، وليس نوعًا.
الحل يكمن في مجال الانعكاس (Reflection) — وهي آلية تسمح بفحص واستدعاء أعضاء الأنواع أثناء التنفيذ.
\n\nدليل تعليمي خطوة بخطوة: استدعاء دالة عامة عبر Type
\nلنحلل المهمة بمثال محدد. لنفترض أن لدينا خدمة لمعالجة البيانات:
\npublic class DataProcessor\n{\n public string Process<T>(T data)\n {\n return $"تمت معالجة {typeof(T).Name}: {data}";\n }\n}\n\n\nنريد استدعاء Process<T> عندما يكون النوع T معروفًا فقط في وقت التشغيل، على سبيل المثال، من typeof(string) أو someObject.GetType().
الخطوة 1. الحصول على MethodInfo للدالة العامة
\nأولاً، نحتاج إلى الحصول على كائن يمثل الدالة الخاصة بنا:
\nDataProcessor processor = new DataProcessor();\nType processorType = typeof(DataProcessor);\n\n// نبحث عن الدالة بالاسم. مهم: نحدد BindingFlags إذا كانت الدالة غير عامة (non-public)\nMethodInfo? methodInfo = processorType.GetMethod("Process");\n\n\nمهم! إذا كان لديك عدة تحميلات زائدة (overloads) للدالة، ستحتاج إلى توضيح البحث باستخدام GetMethods() والتصفية. في حالتنا، الدالة واحدة — كل شيء بسيط.
الخطوة 2. تحويل الدالة العامة إلى دالة محددة باستخدام MakeGenericMethod
\nها هو، بطل المقال — الدالة MakeGenericMethod. تأخذ مصفوفة من الأنواع وتعيد دالة مُنشأة (محددة):
Type runtimeType = typeof(int); // أو أي نوع آخر تم الحصول عليه ديناميكيًا\nMethodInfo? genericMethod = methodInfo?.MakeGenericMethod(runtimeType);\n\n\nالآن genericMethod هي دالة كاملة Process<int>، جاهزة للاستدعاء.
الخطوة 3. استدعاء الدالة عبر Invoke
\nاللمسة النهائية — استخدام Invoke:
object? result = genericMethod?.Invoke(processor, new object[] { 42 });\nConsole.WriteLine(result); // سيُظهر: "تمت معالجة Int32: 42"\n\n\nلاحظ ما يلي:
\n- \n
- المعامل الأول لـ
Invokeهو نسخة الكائن (للدوال الثابتة —null). \n - المعامل الثاني هو مصفوفة من وسائط الدالة. حتى لو كان الوسيط واحدًا، يجب أن يكون في مصفوفة. \n
- النتيجة تُعاد كـ
object?— قد تحتاج إلى تحويلها (cast) إلى النوع المناسب. \n