मैं जेनेरिक पद्धति को कॉल करने के लिए प्रतिबिंब का उपयोग कैसे करूं?


1069

जब सामान्य पैरामीटर को संकलन समय पर नहीं जाना जाता है, तो सामान्य तरीके से कॉल करने का सबसे अच्छा तरीका क्या है, लेकिन इसके बजाय गतिशील रूप से रनटाइम पर प्राप्त किया जाता है?

निम्नलिखित नमूना कोड पर विचार करें - Example()विधि के अंदर , चर में संग्रहीत GenericMethod<T>()का उपयोग करने के लिए सबसे संक्षिप्त तरीका क्या है ?TypemyType

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>()
    {
        //...
    }
}

7
मैंने जॉन के समाधान की कोशिश की और इसे तब तक काम करने के लिए नहीं मिला जब तक मैंने अपनी कक्षा में सामान्य विधि को सार्वजनिक नहीं किया। मुझे पता है कि एक अन्य जॉन ने कहा कि आपको बाइंडफ्लैग निर्दिष्ट करने की आवश्यकता है लेकिन इससे कोई फायदा नहीं हुआ।
naskew

12
निजी / आंतरिक विधि प्राप्त करने के लिए भी आपको BindingFlags.Instanceसिर्फ जरूरत नहीं है BindingFlags.NonPublic
लार्स केम्मन

2
इस सवाल का आधुनिक संस्करण: stackoverflow.com/q/2433436/103167
बेन Voigt

@Peter मोर्टेनसेन - फी मैं 'से पहले रिक्त स्थान का इस्तेमाल किया? गैर-अंग्रेजी (C #) भागों से अंग्रेजी भागों को अलग करने के लिए; IMHO अंतरिक्ष को हटाने से यह कैसा दिखता है? कोड का हिस्सा है। यदि कोई कोड नहीं था, तो मैं निश्चित रूप से रिक्त स्थान को हटाने से सहमत हूं, लेकिन इस मामले में ...
Bevan

जवाबों:


1137

शुरुआत करने के लिए आपको विधि का उपयोग करने के लिए प्रतिबिंब का उपयोग करने की आवश्यकता है, फिर MakeGenericMethod के साथ प्रकार तर्क देकर इसे "निर्माण" करें :

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

स्थिर विधि के लिए, nullपहले तर्क के रूप में पास करें Invoke। यह सामान्य तरीकों से कोई लेना-देना नहीं है - यह सिर्फ सामान्य प्रतिबिंब है।

जैसा कि उल्लेख किया गया है, इसका उपयोग करना C # 4 के रूप में बहुत सरल है dynamic- यदि आप निश्चित रूप से प्रकार के अनुमान का उपयोग कर सकते हैं। यह उन मामलों में मदद नहीं करता है, जहां प्रकार का अनुमान उपलब्ध नहीं है, जैसे कि प्रश्न में सटीक उदाहरण।


92
+1; ध्यान दें कि GetMethod()केवल डिफ़ॉल्ट रूप से सार्वजनिक इंस्टेंस विधियों पर विचार करता है, इसलिए आपको आवश्यकता हो सकती है BindingFlags.Staticऔर / या BindingFlags.NonPublic

20
झंडे का सही संयोजन BindingFlags.NonPublic | BindingFlags.Instance(और वैकल्पिक रूप से BindingFlags.Static) है।
लार्स केम्मन

4
इस प्रश्न के बारे में पता चलता है कि यह आश्चर्यजनक तरीकों से कैसे किया जा सकता है - और तकनीकी रूप से यहाँ यह प्रश्न है। जेनेरिक.इनवोक () का पहला पैरामीटर स्थिर विधियों को कॉल करते समय अशक्त होना चाहिए। इंस्टेंस मेथड को कॉल करते समय पहला पैरामीटर केवल आवश्यक है।
क्रिस मोशचिनी

2
@ChrisMoschini: कि जवाब में जोड़ा गया।
जॉन स्कीट

2
@gzou: मैं जवाब देने के लिए जोड़ दिया है - लेकिन ध्यान दें कि सामान्य तरीकों फोन करने के लिए सवाल में , dynamicमदद क्योंकि प्रकार निष्कर्ष उपलब्ध नहीं है नहीं करता है। (ऐसा कोई तर्क नहीं है कि कंपाइलर टाइप तर्क को निर्धारित करने के लिए उपयोग कर सकते हैं।)
जॉन स्कीट

170

मूल उत्तर के लिए बस एक अतिरिक्त। जबकि यह काम करेगा:

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

यह भी थोड़ा खतरनाक है कि आप इसके लिए संकलन-समय की जांच खो देते हैं GenericMethod। यदि आप बाद में एक रीफैक्टरिंग और नाम बदलते हैं GenericMethod, तो यह कोड नोटिस नहीं करेगा और रन टाइम पर विफल हो जाएगा। इसके अलावा, अगर असेंबली का कोई भी पोस्ट-प्रोसेसिंग है (उदाहरण के लिए अप्रयुक्त तरीकों को हटाने या अप्रयुक्त तरीकों / वर्गों को) तो यह कोड भी टूट सकता है।

इसलिए, यदि आप उस विधि को जानते हैं जिसे आप संकलन समय पर लिंक कर रहे हैं, और यह लाखों बार नहीं कहा जाता है, तो ओवरहेड कोई फर्क नहीं पड़ता, मैं इस कोड को बदल दूंगा:

Action<> GenMethod = GenericMethod<int>;  //change int by any base type 
                                          //accepted by GenericMethod
MethodInfo method = this.GetType().GetMethod(GenMethod.Method.Name);
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

बहुत सुंदर नहीं होने के बावजूद, आपके पास यहां एक संकलन समय का संदर्भ GenericMethodहै, और यदि आप कुछ भी रिफ्लेक्टर करते हैं, हटाते हैं या करते हैं GenericMethod, तो यह कोड काम करता रहेगा, या कम से कम संकलन समय पर (यदि आप हटाते हैं तो उदाहरण के लिए GenericMethod)।

ऐसा करने का अन्य तरीका एक नया आवरण वर्ग बनाना, और इसके माध्यम से बनाना होगा Activator। मुझे नहीं पता कि क्या कोई बेहतर तरीका है।


5
ऐसे मामलों में जहां प्रतिबिंब को एक विधि को कॉल करने के लिए उपयोग किया जाता है, यह सामान्य है कि विधि का नाम स्वयं किसी अन्य विधि द्वारा खोजा जाता है। अग्रिम में विधि का नाम जानना आम नहीं है।
बेवन

13
खैर, मैं प्रतिबिंब के सामान्य उपयोग के लिए सहमत हूं। लेकिन मूल प्रश्न यह था कि "GenericMethod <myType> (") को कैसे कॉल किया जाए, अगर उस वाक्यविन्यास को अनुमति दी जाए, तो हमें GetMethod () की बिल्कुल भी आवश्यकता नहीं होगी। लेकिन सवाल के लिए "मैं कैसे लिखता हूं" GenericMethod <myType> "? मुझे लगता है कि जवाब में GenericMethod के साथ संकलन-समय लिंक को खोने से बचने का एक तरीका शामिल होना चाहिए। अब यदि यह प्रश्न आम है या नहीं, तो मुझे नहीं पता, लेकिन मुझे पता है कि मुझे कल यह सटीक समस्या थी, और इसीलिए मैं इस प्रश्न में उतरा।
एड्रियन गैलेरो

20
आप GenMethod.Method.GetGenericMethodDefinition()इसके बजाय कर सकते हैं this.GetType().GetMethod(GenMethod.Method.Name)। यह थोड़ा साफ है और शायद सुरक्षित है।
डैनियल कैसिडी

आपके नमूने में "myType" का क्या अर्थ है?
डेवलेपर

37
अब आप उपयोग कर सकते हैंnameof(GenericMethod)
dmigo

140

केवल रनटाइम पर ज्ञात एक प्रकार के पैरामीटर के साथ एक सामान्य विधि को कॉल dynamicकरना प्रतिबिंब एपीआई के बजाय एक प्रकार का उपयोग करके बहुत सरल किया जा सकता है।

इस तकनीक का उपयोग करने के लिए प्रकार को वास्तविक वस्तु (केवल Typeकक्षा का एक उदाहरण नहीं ) से जाना जाना चाहिए । अन्यथा, आपको उस प्रकार का ऑब्जेक्ट बनाना होगा या मानक प्रतिबिंब एपीआई समाधान का उपयोग करना होगा । आप Activator.CreateInstance पद्धति का उपयोग करके ऑब्जेक्ट बना सकते हैं ।

यदि आप एक सामान्य विधि को कॉल करना चाहते हैं, कि "सामान्य" उपयोग में इसके प्रकार का अनुमान लगाया गया होगा, तो यह केवल अज्ञात प्रकार के ऑब्जेक्ट को कास्टिंग करने के लिए आता है dynamic। यहाँ एक उदाहरण है:

class Alpha { }
class Beta { }
class Service
{
    public void Process<T>(T item)
    {
        Console.WriteLine("item.GetType(): " + item.GetType()
                          + "\ttypeof(T): " + typeof(T));
    }
}

class Program
{
    static void Main(string[] args)
    {
        var a = new Alpha();
        var b = new Beta();

        var service = new Service();
        service.Process(a); // Same as "service.Process<Alpha>(a)"
        service.Process(b); // Same as "service.Process<Beta>(b)"

        var objects = new object[] { a, b };
        foreach (var o in objects)
        {
            service.Process(o); // Same as "service.Process<object>(o)"
        }
        foreach (var o in objects)
        {
            dynamic dynObj = o;
            service.Process(dynObj); // Or write "service.Process((dynamic)o)"
        }
    }
}

और यहाँ इस कार्यक्रम का उत्पादन है:

item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta
item.GetType(): Alpha    typeof(T): System.Object
item.GetType(): Beta     typeof(T): System.Object
item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta

Processएक जेनेरिक इंस्टेंस विधि है जो पास किए गए तर्क के वास्तविक प्रकार ( GetType()विधि का उपयोग करके ) और सामान्य पैरामीटर के प्रकार ( typeofऑपरेटर का उपयोग करके ) लिखता है ।

ऑब्जेक्ट तर्क को dynamicटाइप करने के लिए हमने रनटाइम तक टाइप पैरामीटर प्रदान करने को स्थगित कर दिया। जब Processविधि को dynamicतर्क के साथ बुलाया जाता है तो संकलक इस तर्क के प्रकार के बारे में परवाह नहीं करता है। संकलक कोड उत्पन्न करता है कि रनटाइम में वास्तविक प्रकार के पारित तर्कों (प्रतिबिंब का उपयोग करके) की जांच करता है और कॉल करने के लिए सबसे अच्छा तरीका चुनता है। यहाँ केवल यह एक सामान्य विधि है, इसलिए इसे उचित प्रकार के पैरामीटर के साथ लागू किया जाता है।

इस उदाहरण में, आउटपुट वैसा ही है जैसा आपने लिखा है:

foreach (var o in objects)
{
    MethodInfo method = typeof(Service).GetMethod("Process");
    MethodInfo generic = method.MakeGenericMethod(o.GetType());
    generic.Invoke(service, new object[] { o });
}

गतिशील प्रकार वाला संस्करण निश्चित रूप से छोटा और लिखने में आसान है। आपको कई बार इस फ़ंक्शन को कॉल करने के प्रदर्शन के बारे में चिंता नहीं करनी चाहिए। उसी प्रकार के तर्कों के साथ अगली कॉल डीएलआर में कैशिंग तंत्र के लिए तेजी से धन्यवाद होनी चाहिए । बेशक, आप उस कोड को लिख सकते हैं जो कैश ने प्रतिनिधियों को आमंत्रित किया है, लेकिन जिस dynamicप्रकार का उपयोग करके आपको यह व्यवहार मुफ्त में मिलता है।

यदि आप जिस जेनेरिक विधि को कॉल करना चाहते हैं, उसमें पैरामीरिज्ड प्रकार का तर्क नहीं है (इसलिए इसके प्रकार के पैरामीटर का अनुमान नहीं लगाया जा सकता है) तो आप निम्न उदाहरण में जेनेरिक पद्धति के आह्वान को सहायक विधि से लपेट सकते हैं:

class Program
{
    static void Main(string[] args)
    {
        object obj = new Alpha();

        Helper((dynamic)obj);
    }

    public static void Helper<T>(T obj)
    {
        GenericMethod<T>();
    }

    public static void GenericMethod<T>()
    {
        Console.WriteLine("GenericMethod<" + typeof(T) + ">");
    }
}

बढ़ी हुई प्रकार की सुरक्षा

dynamicप्रतिबिंब एपीआई का उपयोग करने के प्रतिस्थापन के रूप में ऑब्जेक्ट का उपयोग करने के बारे में वास्तव में बहुत अच्छा है कि आप केवल इस विशेष प्रकार की संकलन समय की जांच खो देते हैं जिसे आप रनटाइम तक नहीं जानते हैं। अन्य तर्क और विधि का नाम हमेशा की तरह संकलक द्वारा स्थिर रूप से विश्लेषण किया जाता है। यदि आप अधिक तर्कों को हटाते हैं या जोड़ते हैं, तो उनके प्रकार या नाम विधि का नाम बदलें, फिर आपको एक संकलन-समय त्रुटि मिलेगी। यह तब नहीं होगा जब आप विधि नाम को स्ट्रिंग के रूप में प्रदान करते हैं Type.GetMethodऔर ऑब्जेक्ट में सरणी के रूप में तर्क देते हैं MethodInfo.Invoke

नीचे एक सरल उदाहरण दिया गया है, जो दिखाता है कि संकलन समय (टिप्पणी कोड) और रनटाइम के दौरान कुछ त्रुटियों को कैसे पकड़ा जा सकता है। यह यह भी दिखाता है कि DLR किस विधि को कॉल करने की कोशिश करता है।

interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }

class Program
{
    static void Main(string[] args)
    {
        var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
        for (int i = 0; i < objects.Length; i++)
        {
            ProcessItem((dynamic)objects[i], "test" + i, i);

            //ProcesItm((dynamic)objects[i], "test" + i, i);
            //compiler error: The name 'ProcesItm' does not
            //exist in the current context

            //ProcessItem((dynamic)objects[i], "test" + i);
            //error: No overload for method 'ProcessItem' takes 2 arguments
        }
    }

    static string ProcessItem<T>(T item, string text, int number)
        where T : IItem
    {
        Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
                          typeof(T), text, number);
        return "OK";
    }
    static void ProcessItem(BarItem item, string text, int number)
    {
        Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
    }
}

यहाँ हम फिर से dynamicटाइप का तर्क देकर कुछ विधि निष्पादित करते हैं । केवल पहले तर्क के प्रकार का सत्यापन रनटाइम के लिए स्थगित कर दिया गया है। यदि आप जिस विधि को कॉल कर रहे हैं उसका नाम मौजूद नहीं है या यदि अन्य तर्क अमान्य हैं (तर्कों की गलत संख्या या गलत प्रकार) तो आपको संकलक त्रुटि मिलेगी।

जब आप dynamicकिसी विधि पर तर्क पास करते हैं तो यह कॉल हाल ही में बाध्य होती है । विधि अधिभार संकल्प रनटाइम पर होता है और सबसे अच्छा अधिभार चुनने की कोशिश करता है। इसलिए यदि आप ProcessItemविधि को ऑब्जेक्ट के साथ BarItemटाइप करते हैं तो आप वास्तव में गैर-जेनेरिक विधि को कॉल करेंगे, क्योंकि यह इस प्रकार के लिए एक बेहतर मेल है। हालाँकि, जब आप Alphaटाइप का एक तर्क पास करते हैं तो आपको एक रनटाइम त्रुटि मिलेगी क्योंकि कोई भी तरीका नहीं है जो इस ऑब्जेक्ट को संभाल सकता है (एक सामान्य विधि में बाधा है where T : IItemऔर Alphaक्लास इस इंटरफ़ेस को लागू नहीं करता है)। लेकिन यह पूरी बात है। कंपाइलर के पास यह जानकारी नहीं है कि यह कॉल वैध है। आप एक प्रोग्रामर के रूप में यह जानते हैं, और आपको यह सुनिश्चित करना चाहिए कि यह कोड बिना त्रुटियों के चलता है।

रिटर्न प्रकार गोच

आप गतिशील प्रकार का एक पैरामीटर के साथ एक गैर शून्य विधि बुला रहे हैं, अपनी वापसी प्रकार शायद होगा होना dynamicभी । इसलिए यदि आप पिछले उदाहरण को इस कोड में बदलेंगे:

var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

तब परिणाम वस्तु का प्रकार होगा dynamic। ऐसा इसलिए है क्योंकि कंपाइलर को हमेशा नहीं पता होता है कि किस विधि को बुलाया जाएगा। यदि आप फ़ंक्शन कॉल का रिटर्न प्रकार जानते हैं, तो आपको इसे आवश्यक प्रकार में बदलना चाहिए ताकि बाकी कोड सांख्यिकीय रूप से टाइप हो जाए:

string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

यदि प्रकार मेल नहीं खाता, तो आपको रनटाइम त्रुटि मिलेगी।

दरअसल, यदि आप पिछले उदाहरण में परिणाम मान प्राप्त करने का प्रयास करते हैं तो आपको दूसरे लूप पुनरावृत्ति में रनटाइम त्रुटि मिलेगी। ऐसा इसलिए है क्योंकि आपने एक शून्य फ़ंक्शन के रिटर्न मान को बचाने की कोशिश की है।


Mariusz, "हालाँकि, जब आप अल्फा प्रकार का तर्क पास करते हैं, तो आपको रनटाइम त्रुटि प्राप्त होगी क्योंकि इस पद्धति को संभालने वाली कोई विधि नहीं है।" , i) जेनेरिक ProcessItem विधि इसे प्रभावी ढंग से क्यों नहीं हैंडल करेगी, "सामान्य प्रक्रिया आइटम" का उत्पादन?
एलेक्स एडेलस्टीन

@AlexEdelstein मैंने अपना जवाब थोड़ा स्पष्ट करने के लिए संपादित किया। ऐसा इसलिए है क्योंकि सामान्य ProcessItemविधि में सामान्य बाधा है और IItemइंटरफ़ेस को लागू करने वाली केवल ऑब्जेक्ट को स्वीकार करता है। जब आप कॉल करेंगे ProcessItem(new Aplha(), "test" , 1);या ProcessItem((object)(new Aplha()), "test" , 1);आपको एक कंपाइलर त्रुटि मिलेगी, लेकिन जब dynamicआप कास्ट करते हैं तो रनटाइम की जाँच करें।
मारिउज़ पावेल्स्की

महान जवाब और स्पष्टीकरण, मेरे लिए पूरी तरह से काम करता है। स्वीकृत उत्तर की तुलना में बहुत बेहतर है, लिखने के लिए कम, अधिक प्रदर्शन करने वाला, और सुरक्षित।
9 अगस्ता

17

C # 4.0 के साथ, प्रतिबिंब आवश्यक नहीं है क्योंकि DLR रनटाइम प्रकारों का उपयोग करके इसे कॉल कर सकता है। चूंकि DLR लाइब्रेरी का उपयोग गतिशील रूप से एक दर्द की तरह है (आपके लिए C # कंपाइलर जनरेटिंग कोड के बजाय), ओपन सोर्स फ्रेमवर्क Dynamitey (.net standard 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}));

13

एड्रियन गैलेरो के जवाब में जोड़ना :

टाइप जानकारी से एक सामान्य विधि को कॉल करने में तीन चरण शामिल हैं।

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 के एक अलग अधिभार को संदर्भित करता है

विधि 3: विधि कॉल एक्सप्रेशन वाली लैम्ब्डा अभिव्यक्ति बनाएँ, MethodInfo ऑब्जेक्ट प्राप्त करें और फिर GetGenericMethodDefinition

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

यह करने के लिए टूट जाता है

एक लैम्ब्डा एक्सप्रेशन बनाएं जहां शरीर आपकी इच्छित विधि के लिए एक कॉल है।

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);

8

किसी ने " क्लासिक प्रतिबिंब " समाधान प्रदान नहीं किया है , इसलिए यहां एक पूर्ण कोड उदाहरण है:

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)

और यह एक IDEDIA उदाहरण बनाता है और लौटाता है, जिनके प्रकार की कुंजी और मान कॉल पर निर्दिष्ट हैं 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

2

यह ग्रेक्स के उत्तर पर आधारित मेरे 2 सेंट हैं , लेकिन एक सामान्य विधि के लिए आवश्यक दो मापदंडों के साथ।

मान लें कि आपकी विधि एक सहायक वर्ग में निम्नानुसार परिभाषित की गई है:

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

मेरे मामले में, यू टाइप हमेशा टी का एक ऑब्जर्वेबल संग्रह होता है जो टाइप टी का उद्देश्य होता है।

जैसा कि मैंने अपने प्रकारों को पूर्वनिर्धारित किया है, मैं पहली बार "डमी" ऑब्जेक्ट बनाता हूं जो देखने योग्य संग्रह (यू) और उसमें रखी गई वस्तु (टी) का प्रतिनिधित्व करते हैं और जिनका उपयोग मेक कॉल करते समय उनके प्रकार प्राप्त करने के लिए नीचे किया जाएगा।

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

फिर अपने जेनेरिक फ़ंक्शन को खोजने के लिए GetMethod को कॉल करें:

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

अब तक, उपरोक्त कॉल बहुत अधिक है जैसा कि ऊपर बताया गया था लेकिन एक छोटे अंतर के साथ जब आपको इसके लिए कई मापदंडों को पास करना पड़ता है।

आपको MakeGenericMethod फ़ंक्शन में एक प्रकार [] सरणी पास करने की आवश्यकता है जिसमें "डमी" ऑब्जेक्ट्स हैं जो ऊपर बनाए गए हैं:

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

एक बार ऐसा करने के बाद, आपको ऊपर बताए अनुसार इनवोक विधि को कॉल करना होगा।

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

और आपने कल लिया। एक आकर्षण काम करता है!

अपडेट करें:

जैसा कि @ बेवन ने प्रकाश डाला, मुझे मेक गेनिकमेथोड फ़ंक्शन को कॉल करते समय एक सरणी बनाने की आवश्यकता नहीं है क्योंकि यह परमेस में लेता है और मुझे टाइप करने के लिए ऑब्जेक्ट बनाने की आवश्यकता नहीं है क्योंकि मैं इस फ़ंक्शन को सीधे टाइप कर सकता हूं। मेरे मामले में, चूंकि मेरे पास किसी अन्य वर्ग में पूर्वनिर्धारित प्रकार हैं, मैंने बस अपना कोड बदल दिया है:

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 में 2 प्रकार के गुण Typeहोते हैं, जो मैं निर्माणकर्ता को दिए गए एक एनम मान के आधार पर रन टाइम पर सेट करता हूं और मुझे प्रासंगिक प्रकारों के साथ प्रदान करेगा, जो तब मैं मेक जेनरिकमेथोड में उपयोग करता हूं।

इस @Bevan को उजागर करने के लिए फिर से धन्यवाद।


बहस के लिए MakeGenericMethod()है पैरामीटर कीवर्ड ताकि आप एक सरणी बनाने की जरूरत नहीं है, न ही आपको प्रकार प्राप्त करने के लिए उदाहरण बनाने की आवश्यकता है - methodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject))पर्याप्त होगा।
बेवन

0

Enigmativity के उत्तर से प्रेरित - मान लें कि आपके पास दो (या अधिक) कक्षाएं हैं, जैसे

public class Bar { }
public class Square { }

और आप विधि कॉल करना चाहते हैं Foo<T>के साथ Barऔर Squareहै, जो के रूप में घोषित किया जाता है

public class myClass
{
    public void Foo<T>(T item)
    {
        Console.WriteLine(typeof(T).Name);
    }
}

फिर आप एक एक्सटेंशन विधि लागू कर सकते हैं जैसे:

public static class Extension
{
    public static void InvokeFoo<T>(this T t)
    {
        var fooMethod = typeof(myClass).GetMethod("Foo");
        var tType = typeof(T);
        var fooTMethod = fooMethod.MakeGenericMethod(new[] { tType });
        fooTMethod.Invoke(new myClass(), new object[] { t });
    }
}

इसके साथ, आप बस इस Fooतरह आमंत्रित कर सकते हैं:

var objSquare = new Square();
objSquare.InvokeFoo();

var objBar = new Bar();
objBar.InvokeFoo();

जो हर वर्ग के लिए काम करता है। इस मामले में, यह आउटपुट होगा:

चौकोर
बार

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.