sharp - شرح method c#




كيف يمكنني استخدام التفكير في استدعاء طريقة عامة؟ (5)

ما هي أفضل طريقة لاستدعاء طريقة عامة عندما لا تكون معلمة النوع معروفة في وقت التحويل البرمجي ، ولكن يتم الحصول عليها بدلاً من ذلك ديناميكيًا في وقت التشغيل؟

النظر في نموذج التعليمات البرمجية التالي - داخل أسلوب Example() ، ما هي الطريقة الأكثر إيجازاً لاستدعاء GenericMethod<T>() باستخدام Type المخزنة في متغير myType ؟

public class Sample
{
    public void Example(string typeName)
    {
        Type myType = FindType(typeName);

        // What goes here to call GenericMethod<T>()?
        GenericMethod<myType>(); // This doesn't work

        // What changes to call StaticMethod<T>()?
        Sample.StaticMethod<myType>(); // This also doesn't work
    }

    public void GenericMethod<T>()
    {
        // ...
    }

    public static void StaticMethod<T>()
    {
        //...
    }
}

إضافة إلى إجابة أدريان جاليرو :

يتضمن استدعاء طريقة عامة من معلومات النوع ثلاث خطوات.

TLDR: يمكن تحقيق استدعاء طريقة عامة معروفة بكائن كتابة بواسطة:

((Action)GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition()
    .MakeGenericMethod(typeof(string))
    .Invoke(this, null);

حيث GenericMethod<object> هو اسم الأسلوب الذي سيتم الاتصال به وأي نوع يفي بالقيود العامة.

(الإجراء) يطابق توقيع الأسلوب المراد تسميته ( Func<string,string,int> أو Action<bool> )

الخطوة 1 هو الحصول على MethodInfo من أجل تعريف الأسلوب العام

الأسلوب 1: استخدام GetMethod () أو GetMethods () مع الأنواع المناسبة أو علامات الربط.

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");

الطريقة 2: إنشاء مفوض ، الحصول على كائن MethodInfo ثم استدعاء GetGenericMethodDefinition

من داخل الفصل الذي يحتوي على الطرق:

MethodInfo method = ((Action)GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();

من خارج الفصل الذي يحتوي على الطرق:

MethodInfo method = ((Action)(new Sample())
    .GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)Sample.StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();

في C # ، يشير اسم الأسلوب ، أي "ToString" أو "GenericMethod" فعليًا إلى مجموعة من الطرق التي قد تحتوي على طريقة واحدة أو أكثر. حتى يتم تقديم أنواع معلمات الطريقة ، لا يعرف الأسلوب الذي تشير إليه.

((Action)GenericMethod<object>) يشير إلى المفوض عن طريقة معينة. ((Func<string, int>)GenericMethod<object>) يشير إلى حمل زائد مختلف من GenericMethod

الطريقة الثالثة: إنشاء تعبير lambda يحتوي على تعبير استدعاء أسلوب ، الحصول على كائن MethodInfo ثم GetGenericMethodDefinition

MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)(
    (Sample v) => v.GenericMethod<object>()
    )).Body).Method.GetGenericMethodDefinition();

هذا ينهار ل

قم بإنشاء تعبير lambda حيث يكون الجسم عبارة عن استدعاء للطريقة التي تريدها.

Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();

استخراج الجسم ويلقي على MethodCallExpression

MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;

احصل على تعريف الأسلوب العام من الطريقة

MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();

الخطوة 2 استدعاء MakeGenericMethod لإنشاء أسلوب عام بنوع (أنواع) المناسبة.

MethodInfo generic = method.MakeGenericMethod(myType);

الخطوة 3 هي استدعاء الأسلوب مع الوسيطات المناسبة.

generic.Invoke(this, null);

تحتاج إلى استخدام الانعكاس للحصول على طريقة للبدء بها ، ثم "إنشاء" من خلال توفير وسائط النوع باستخدام MakeGenericMethod :

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

بالنسبة لطريقة ثابتة ، قم بتمرير null كوسيطة أول إلى Invoke . هذا لا علاقة له بالطرق العامة - إنه مجرد التفكير العادي.

كما ذكرنا ، فإن الكثير من هذا هو أبسط من C # 4 باستخدام dynamic - إذا كنت تستخدم نوع الاستدلال ، بالطبع. لا يساعد ذلك في الحالات التي لا يتوفر فيها نوع الاستدلال ، مثل المثال الدقيق في السؤال.


مع C # 4.0 ، لا يكون الانعكاس ضروريًا حيث يمكن لـ DLR الاتصال به باستخدام أنواع وقت التشغيل. نظرًا لأن استخدام مكتبة DLR هو نوع من الألم بشكل ديناميكي (بدلاً من رمز إنشاء برنامج التحويل البرمجي C # لك) ، فإن إطار المصدر المفتوح Dynamitey (.net 1.5 القياسي) يمنحك سهولة الوصول في وقت التشغيل المخزن إلى نفس المكالمات التي قد يولدها المحول البرمجي لك.

var name = InvokeMemberName.Create;
Dynamic.InvokeMemberAction(this, name("GenericMethod", new[]{myType}));


var staticContext = InvokeContext.CreateStatic;
Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType}));

هذا هو 2 سنت لي على أساس إجابة Grax ، ولكن مع اثنين من المعلمات المطلوبة لطريقة عامة.

افترض أن يتم تعريف الأسلوب الخاص بك كما يلي في فئة Helpers:

public class Helpers
{
    public static U ConvertCsvDataToCollection<U, T>(string csvData)
    where U : ObservableCollection<T>
    {
      //transform code here
    }
}

في حالتي ، يكون U type دائمًا عبارة عن كائن تخزين يمكن ملاحظته وتخزينه من النوع T.

بما أن أنواعي معرّفة مسبقًا ، فأنا أولاً أنشئ الكائنات "الوهمية" التي تمثل المجموعة التي يمكن ملاحظتها (U) والكائن المخزن فيها (T) والتي سيتم استخدامها أدناه للحصول على نوعها عند استدعاء Make

object myCollection = Activator.CreateInstance(collectionType);
object myoObject = Activator.CreateInstance(objectType);

اتصل بـ GetMethod للعثور على وظيفة Generic لديك:

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

حتى الآن ، فإن المكالمة المذكورة أعلاه متطابقة إلى حد كبير مع ما تم شرحه أعلاه ولكن مع اختلاف بسيط عند الحاجة إلى تمرير معلمات متعددة إليه.

تحتاج إلى تمرير صفيف Type [] إلى الدالة MakeGenericMethod التي تحتوي على أنواع الكائنات "الوهمية" التي تم إنشاؤها أعلاه:

MethodInfo generic = method.MakeGenericMethod(
new Type[] {
   myCollection.GetType(),
   myObject.GetType()
});

بمجرد الانتهاء ، تحتاج إلى استدعاء الأسلوب Invoke كما هو مذكور أعلاه.

generic.Invoke(null, new object[] { csvData });

إنتهيت. يعمل سحر!

تحديث:

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

object myCollection = null;

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

MethodInfo generic = method.MakeGenericMethod(
   myClassInfo.CollectionType,
   myClassInfo.ObjectType
);

myCollection = generic.Invoke(null, new object[] { csvData });

يحتوي myClassInfo على خاصيتين من النوع Type الذي قمت بتعيينه في وقت التشغيل استنادًا إلى قيمة التعداد التي تم تمريرها إلى المُنشئ وسوف توفر لي الأنواع ذات الصلة التي استخدمتها بعد ذلك في MakeGenericMethod.

شكرا مرة أخرى لتسليط الضوء على هذا @ Bevan.


لم يقدم أحد الحل " التأمّل الكلاسيكي " ، لذا إليك مثال رمز كامل:

using System;
using System.Collections;
using System.Collections.Generic;

namespace DictionaryRuntime
{
    public class DynamicDictionaryFactory
    {
        /// <summary>
        /// Factory to create dynamically a generic Dictionary.
        /// </summary>
        public IDictionary CreateDynamicGenericInstance(Type keyType, Type valueType)
        {
            //Creating the Dictionary.
            Type typeDict = typeof(Dictionary<,>);

            //Creating KeyValue Type for Dictionary.
            Type[] typeArgs = { keyType, valueType };

            //Passing the Type and create Dictionary Type.
            Type genericType = typeDict.MakeGenericType(typeArgs);

            //Creating Instance for Dictionary<K,T>.
            IDictionary d = Activator.CreateInstance(genericType) as IDictionary;

            return d;

        }
    }
}

طبقة DynamicDictionaryFactory المذكورة أعلاه لها طريقة

CreateDynamicGenericInstance(Type keyType, Type valueType)

ويقوم بإنشاء وإرجاع مثيل IDictionary ، وأنواع المفاتيح والقيم الخاصة بها هي المحددة تمامًا في keyType الاتصال keyType و valueType .

فيما يلي مثال كامل على كيفية استدعاء هذا الأسلوب لإنشاء مثيل واستخدام Dictionary<String, int> :

using System;
using System.Collections.Generic;

namespace DynamicDictionary
{
    class Test
    {
        static void Main(string[] args)
        {
            var factory = new DictionaryRuntime.DynamicDictionaryFactory();
            var dict = factory.CreateDynamicGenericInstance(typeof(String), typeof(int));

            var typedDict = dict as Dictionary<String, int>;

            if (typedDict != null)
            {
                Console.WriteLine("Dictionary<String, int>");

                typedDict.Add("One", 1);
                typedDict.Add("Two", 2);
                typedDict.Add("Three", 3);

                foreach(var kvp in typedDict)
                {
                    Console.WriteLine("\"" + kvp.Key + "\": " + kvp.Value);
                }
            }
            else
                Console.WriteLine("null");
        }
    }
}

عندما يتم تنفيذ تطبيق وحدة التحكم أعلاه ، نحصل على النتيجة الصحيحة والمتوقعة:

Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3




reflection