كيفية استدعاء دالة عامة (generic method) باستخدام Type في C#: دليل تعليمي واضح

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

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

ابدأ الدورة

مقدمة: عندما يقول الكود «لا أعرف»

\n

تخيل موقفًا: أنت تكتب مكتبة تسلسل (serialization) بلغة C#. لديك دالة Serialize<T>(T obj) يمكنها تحويل أي كائن إلى سلسلة نصية. كل شيء رائع طالما تستدعيها بنوع محدد: Serialize<User>(user). لكن ماذا تفعل إذا أصبح نوع الكائن معروفًا فقط أثناء تنفيذ البرنامج؟

\n\n

هذه مشكلة كلاسيكية يطرحها آلاف المطورين على Stack Overflow: «كيف أستدعي دالة عامة (generic method) باستخدام متغير Type؟». للوهلة الأولى، يبدو الحل بسيطًا، لكن C# لغة شديدة التحديد في الأنواع (strongly-typed)، ولا يمكنك ببساطة وضع متغير نوع داخل الأقواس الزاوية.

\n\n

في هذا الدليل التعليمي، سنشرح كيفية استدعاء الدوال العامة بشكل صحيح عبر الانعكاس (Reflection) في C# باستخدام MakeGenericMethod. ستتعلم ليس فقط الصياغة (syntax)، ولكن أيضًا متى يكون ذلك ضروريًا حقًا، ومتى يكون من الأفضل البحث عن بدائل.

\n\n

لماذا لا يمكنك ببساطة كتابة Method<type>()؟

\n

دعنا نبدد الأوهام فورًا. الكود التالي لن يتم تجميعه:

\n
Type runtimeType = someObject.GetType();\n// خطأ في التجميع: "النوع 'runtimeType' غير صالح"\nSomeMethod<runtimeType>();\n
\n\n

لماذا؟ لأن المعاملات العامة (generic parameters) في C# هي بيانات وصفية (metadata) لوقت التجميع. يجب أن يعرف المترجم (compiler) بالضبط النوع الذي يعمل معه لتوليد الكود. المتغير Type هو كائن يعيش فقط في وقت التشغيل (runtime). بالنسبة للمترجم، هو مجرد متغير، وليس نوعًا.

\n\n

الحل يكمن في مجال الانعكاس (Reflection) — وهي آلية تسمح بفحص واستدعاء أعضاء الأنواع أثناء التنفيذ.

\n\n

دليل تعليمي خطوة بخطوة: استدعاء دالة عامة عبر Type

\n

لنحلل المهمة بمثال محدد. لنفترض أن لدينا خدمة لمعالجة البيانات:

\n
public 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().

\n\n

الخطوة 1. الحصول على MethodInfo للدالة العامة

\n

أولاً، نحتاج إلى الحصول على كائن يمثل الدالة الخاصة بنا:

\n
DataProcessor processor = new DataProcessor();\nType processorType = typeof(DataProcessor);\n\n// نبحث عن الدالة بالاسم. مهم: نحدد BindingFlags إذا كانت الدالة غير عامة (non-public)\nMethodInfo? methodInfo = processorType.GetMethod("Process");\n
\n\n

مهم! إذا كان لديك عدة تحميلات زائدة (overloads) للدالة، ستحتاج إلى توضيح البحث باستخدام GetMethods() والتصفية. في حالتنا، الدالة واحدة — كل شيء بسيط.

\n\n

الخطوة 2. تحويل الدالة العامة إلى دالة محددة باستخدام MakeGenericMethod

\n

ها هو، بطل المقال — الدالة MakeGenericMethod. تأخذ مصفوفة من الأنواع وتعيد دالة مُنشأة (محددة):

\n
Type runtimeType = typeof(int); // أو أي نوع آخر تم الحصول عليه ديناميكيًا\nMethodInfo? genericMethod = methodInfo?.MakeGenericMethod(runtimeType);\n
\n\n

الآن genericMethod هي دالة كاملة Process<int>، جاهزة للاستدعاء.

\n\n

الخطوة 3. استدعاء الدالة عبر Invoke

\n

اللمسة النهائية — استخدام Invoke:

\n
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

المدونات

توصيات الكتب