Jak użyć refleksji do wywołania metody ogólnej?

1069

Jaki jest najlepszy sposób na wywołanie metody ogólnej, gdy parametr type nie jest znany w czasie kompilacji, a zamiast tego jest uzyskiwany dynamicznie w czasie wykonywania?

Rozważ następujący przykładowy kod - w jaki Example()sposób najbardziej zwięzły sposób wywołać GenericMethod<T>()za pomocą Typeprzechowywanej w myTypezmiennej zmiennej?

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>()
    {
        //...
    }
}
Bevan
źródło
7
Wypróbowałem rozwiązanie Jona i nie mogłem go uruchomić, dopóki nie opublikowałem ogólnej metody w mojej klasie. Wiem, że inny Jon odpowiedział, że musisz określić flagi wiążące, ale to nie pomogło.
naskew
12
Potrzebujesz także BindingFlags.Instancenie tylko BindingFlags.NonPublicmetody prywatnej / wewnętrznej.
Lars Kemmann
2
Nowoczesna wersja tego pytania: stackoverflow.com/q/2433436/103167
Ben Voigt
@Peter Mortensen - fyi użyłem spacji przed „?” w celu oddzielenia części angielskich od części nieanglojęzycznych (C #); IMHO usunięcie przestrzeni sprawia, że ​​wygląda jak? jest częścią kodu. Gdyby nie było kodu, z pewnością zgodziłbym się na usunięcie spacji, ale w tym przypadku ...
Bevan

Odpowiedzi:

1137

Musisz użyć refleksji, aby rozpocząć metodę, a następnie „skonstruować” ją, podając argumenty typu z MakeGenericMethod :

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

W przypadku metody statycznej przekaż nulljako pierwszy argument do Invoke. To nie ma nic wspólnego z metodami ogólnymi - to tylko normalne odbicie.

Jak wspomniano, wiele z nich jest prostszych niż w C # 4 dynamic- jeśli oczywiście można użyć wnioskowania typu. Nie pomaga w przypadkach, w których wnioskowanie typu nie jest dostępne, takich jak dokładny przykład w pytaniu.

Jon Skeet
źródło
92
+1; zwróć uwagę, że GetMethod()domyślnie uwzględniane są tylko metody instancji publicznych, więc możesz potrzebować BindingFlags.Statici / lub BindingFlags.NonPublic.
20
Prawidłowa kombinacja flag to BindingFlags.NonPublic | BindingFlags.Instance(i opcjonalnie BindingFlags.Static).
Lars Kemmann
4
Pytanie, które zostało oznaczone jako duplikat tego, zastanawia się, jak to zrobić metodami statycznymi - i technicznie tak samo jest tutaj. Pierwszy parametr generic.Invoke () powinien mieć wartość NULL podczas wywoływania metod statycznych. Pierwszy parametr jest konieczny tylko podczas wywoływania metod instancji.
Chris Moschini
2
@ChrisMoschini: Dodano to do odpowiedzi.
Jon Skeet
2
@gzou: Mam coś do odpowiedzi dodaje - ale uwaga, że do wywołania metody rodzajowe w pytaniu , dynamicnie pomaga, ponieważ typ wnioskowanie nie jest dostępna. (Nie ma żadnych argumentów, za pomocą których kompilator może określić argument typu).
Jon Skeet
170

Tylko dodatek do oryginalnej odpowiedzi. Chociaż to zadziała:

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

Jest to również trochę niebezpieczne, ponieważ tracisz kontrolę czasu kompilacji GenericMethod. Jeśli później dokonasz refaktoryzacji i zmienisz nazwę GenericMethod, ten kod nie zauważy tego i zawiedzie w czasie wykonywania. Ponadto, jeśli nastąpi jakiekolwiek przetwarzanie końcowe zestawu (na przykład zaciemnianie lub usuwanie nieużywanych metod / klas), ten kod może również ulec uszkodzeniu.

Tak więc, jeśli znasz metodę, z którą się łączysz w czasie kompilacji, i nie jest to nazywane miliony razy, więc narzut nie ma znaczenia, zmieniłbym ten kod na:

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

Chociaż nie jest to zbyt ładne, masz GenericMethodtutaj odniesienie do czasu kompilacji , a jeśli przefakturujesz, usuniesz lub zrobisz cokolwiek z GenericMethodtym, ten kod będzie działał lub przynajmniej ulegnie awarii w czasie kompilacji (jeśli na przykład usuniesz GenericMethod).

Innym sposobem na zrobienie tego samego byłoby utworzenie nowej klasy opakowania i utworzenie jej przez Activator. Nie wiem, czy jest lepszy sposób.

Adrian Gallero
źródło
5
W przypadkach, w których do wywołania metody używa się odbicia, zwykle nazwa metody jest wykrywana przez inną metodę. Wcześniejsza znajomość nazwy metody nie jest powszechna.
Bevan
13
Zgadzam się na powszechne zastosowania refleksji. Ale pierwotne pytanie brzmiało, jak wywołać „GenericMethod <myType> ()”. Gdyby ta składnia była dozwolona, ​​w ogóle nie potrzebowalibyśmy GetMethod (). Ale jeśli chodzi o pytanie „jak napisać„ GenericMethod <myType> ”? Myślę, że odpowiedź powinna zawierać sposób uniknięcia utraty łącza w czasie kompilacji z GenericMethod. Teraz, jeśli to pytanie jest wspólne, czy nie, nie wiem, ale Wiem, że miałem dokładnie ten problem wczoraj, i dlatego wylądowałem w tym pytaniu
Adrian Gallero,
20
Możesz zrobić GenMethod.Method.GetGenericMethodDefinition()zamiast this.GetType().GetMethod(GenMethod.Method.Name). Jest nieco czystszy i prawdopodobnie bezpieczniejszy.
Daniel Cassidy
Co oznacza „myType” w twojej próbce?
Deweloper
37
Teraz możesz użyćnameof(GenericMethod)
dmigo,
140

Wywołanie metody ogólnej z parametrem typu znanym tylko w czasie wykonywania można znacznie uprościć, stosując dynamictyp zamiast interfejsu API odbicia.

Aby użyć tej techniki, typ musi być znany z rzeczywistego obiektu (nie tylko instancji Typeklasy). W przeciwnym razie musisz utworzyć obiekt tego typu lub użyć standardowego rozwiązania interfejsu API odbicia . Możesz utworzyć obiekt za pomocą metody Activator.CreateInstance .

Jeśli chcesz wywołać metodę ogólną, która w „normalnym” użyciu miałaby wywnioskowany typ, po prostu przychodzi do rzutowania obiektu nieznanego typu na dynamic. Oto przykład:

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)"
        }
    }
}

A oto wynik tego programu:

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

Processjest ogólną metodą instancji, która zapisuje prawdziwy typ przekazywanego argumentu (za pomocą GetType()metody) i typ parametru ogólnego (za pomocą typeofoperatora).

Rzucając argument dynamictypu na typ, odroczyliśmy podawanie parametru type do czasu działania. Gdy Processmetoda jest wywoływana z dynamicargumentem, kompilator nie dba o typ tego argumentu. Kompilator generuje kod, który w czasie wykonywania sprawdza rzeczywiste typy przekazywanych argumentów (za pomocą odbicia) i wybiera najlepszą metodę do wywołania. Tutaj jest tylko jedna ogólna metoda, więc jest wywoływana z odpowiednim parametrem typu.

W tym przykładzie dane wyjściowe są takie same, jakbyś napisał:

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

Wersja z typem dynamicznym jest zdecydowanie krótsza i łatwiejsza do napisania. Nie należy również martwić się wydajnością wywołania tej funkcji wiele razy. Następne wywołanie z argumentami tego samego typu powinno być szybsze dzięki mechanizmowi buforowania w DLR. Oczywiście możesz napisać kod, który buforuje wywoływanych delegatów, ale używając dynamictypu otrzymujesz to zachowanie za darmo.

Jeśli ogólna metoda, którą chcesz wywołać, nie ma argumentu sparametryzowanego (więc nie można wywnioskować jej parametru typu), możesz zawrzeć wywołanie metody ogólnej w metodzie pomocniczej, jak w poniższym przykładzie:

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) + ">");
    }
}

Zwiększone bezpieczeństwo typu

To, co jest naprawdę świetne w używaniu dynamicobiektu jako zamiennika do używania refleksyjnego interfejsu API, polega na tym, że tracisz tylko sprawdzanie czasu kompilacji tego konkretnego typu, którego nie znasz przed uruchomieniem. Inne argumenty i nazwa metody są jak zwykle analizowane statycznie przez kompilator. Jeśli usuniesz lub dodasz więcej argumentów, zmienisz ich typy lub zmienisz nazwę metody, pojawi się błąd kompilacji. Nie stanie się tak, jeśli podasz nazwę metody jako ciąg znaków, Type.GetMethoda argumenty jako tablicę obiektów MethodInfo.Invoke.

Poniżej znajduje się prosty przykład ilustrujący, w jaki sposób niektóre błędy mogą zostać wykryte w czasie kompilacji (kod komentarza), a inne w czasie wykonywania. Pokazuje także, w jaki sposób DLR próbuje rozwiązać metodę, którą należy wywołać.

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

Tutaj ponownie wykonujemy jakąś metodę, rzutując argument na dynamictyp. Tylko weryfikacja typu pierwszego argumentu jest odraczana do środowiska wykonawczego. Otrzymasz błąd kompilatora, jeśli nazwa metody, którą wywołujesz, nie istnieje lub jeśli inne argumenty są niepoprawne (zła liczba argumentów lub niewłaściwe typy).

Gdy przekazujesz dynamicargument do metody, to wywołanie jest ostatnio powiązane . Metoda rozwiązywania przeciążenia ma miejsce w czasie wykonywania i próbuje wybrać najlepsze przeciążenie. Jeśli więc wywołujesz ProcessItemmetodę z obiektem BarItemtypu, wówczas faktycznie wywołasz metodę inną niż ogólna, ponieważ lepiej pasuje do tego typu. Jednak podczas przekazywania argumentu Alphatypu pojawi się błąd czasu wykonania, ponieważ nie ma metody, która mogłaby obsłużyć ten obiekt (metoda ogólna ma ograniczenie, where T : IItema Alphaklasa nie implementuje tego interfejsu). Ale o to chodzi. Kompilator nie ma informacji, że to wywołanie jest prawidłowe. Jako programista wiesz o tym i powinieneś upewnić się, że ten kod działa bez błędów.

Typ zwrotu gotcha

Kiedy dzwoni non-void metody z parametrem dynamicznym typu, jego typ zwracany będzie prawdopodobnie być dynamiczbyt . Więc jeśli zmienisz poprzedni przykład na ten kod:

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

wówczas typ obiektu wynikowego to dynamic. Wynika to z faktu, że kompilator nie zawsze wie, która metoda zostanie wywołana. Jeśli znasz typ zwracanego wywołania funkcji, powinieneś niejawnie przekonwertować go na wymagany typ, aby reszta kodu została wpisana statycznie:

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

Otrzymasz błąd czasu wykonania, jeśli typ nie pasuje.

W rzeczywistości, jeśli spróbujesz uzyskać wartość wyniku w poprzednim przykładzie, otrzymasz błąd czasu wykonania w drugiej iteracji pętli. Jest tak, ponieważ próbowałeś zapisać wartość zwracaną funkcji void.

Mariusz Pawelski
źródło
Mariusz, zdezorientowany przez: „Jednak podczas przekazywania argumentu typu Alpha wystąpi błąd czasu wykonania, ponieważ nie ma metody, która mogłaby obsłużyć ten obiekt.” Jeśli wywołam var a = nowy proces Alpha () ProcessItem (a, „test” + i , i) Dlaczego ogólna metoda ProcessItem nie poradziłaby sobie z tym skutecznie, generując „General Process Item”?
Alex Edelstein
@AlexEdelstein Zredagowałem swoją odpowiedź, aby trochę wyjaśnić. Jest tak, ponieważ ProcessItemmetoda ogólna ma ogólne ograniczenie i akceptuje tylko obiekt, który implementuje IIteminterfejs. Kiedy zadzwonisz ProcessItem(new Aplha(), "test" , 1);lub ProcessItem((object)(new Aplha()), "test" , 1);dostaniesz błąd kompilatora, ale podczas przesyłania do dynamicCiebie odłożysz tę kontrolę do środowiska wykonawczego.
Mariusz Pawelski
Świetna odpowiedź i wyjaśnienie, działa idealnie dla mnie. Znacznie lepsza niż zaakceptowana odpowiedź, krótsza do napisania, bardziej wydajna i bezpieczniejsza.
ygoe
17

W wersji C # 4.0 odbicie nie jest konieczne, ponieważ DLR może je wywoływać przy użyciu typów środowiska wykonawczego. Ponieważ korzystanie z biblioteki DLR jest rodzajem dynamicznego bólu (zamiast kodu generującego kompilator C # dla Ciebie), struktura Open Source Dynamitey (.net standard 1.5) zapewnia łatwy buforowany dostęp w czasie wykonywania do tych samych wywołań, które generowałby kompilator dla Was.

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


var staticContext = InvokeContext.CreateStatic;
Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType}));
jbtule
źródło
13

Uzupełnienie odpowiedzi Adriana Gallero :

Wywołanie metody ogólnej z informacji o typie obejmuje trzy kroki.

TLDR: Wywołanie znanej metody ogólnej z obiektem typu można osiągnąć poprzez:

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

gdzie GenericMethod<object>jest nazwą metody do wywołania i dowolnym typem, który spełnia ogólne ograniczenia.

(Action) pasuje do sygnatury metody, która ma zostać wywołana, tj. (Func<string,string,int> Lub Action<bool>)

Krok 1 to uzyskanie MethodInfo dla ogólnej definicji metody

Metoda 1: Użyj GetMethod () lub GetMethods () z odpowiednimi typami lub flagami wiążącymi.

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

Metoda 2: Utwórz delegata, pobierz obiekt MethodInfo, a następnie wywołaj GetGenericMethodDefinition

Wewnątrz klasy zawierającej metody:

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

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

Spoza klasy, która zawiera metody:

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

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

W języku C # nazwa metody, tj. „ToString” lub „GenericMethod” faktycznie odnosi się do grupy metod, które mogą zawierać jedną lub więcej metod. Dopóki nie podasz typów parametrów metody, nie wiadomo, do której metody się odwołujesz.

((Action)GenericMethod<object>) odnosi się do delegata dla określonej metody. ((Func<string, int>)GenericMethod<object>) odnosi się do innego przeciążenia GenericMethod

Metoda 3: Utwórz wyrażenie lambda zawierające wyrażenie wywołania metody, pobierz obiekt MethodInfo, a następnie GetGenericMethodDefinition

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

To rozkłada się na

Utwórz wyrażenie lambda, w którym ciało jest wywołaniem wybranej metody.

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

Wyodrębnij treść i rzutuj do MethodCallExpression

MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;

Uzyskaj ogólną definicję metody z tej metody

MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();

Krok 2 wywołuje MakeGenericMethod, aby utworzyć ogólną metodę z odpowiednimi typami.

MethodInfo generic = method.MakeGenericMethod(myType);

Krok 3 polega na wywołaniu metody z odpowiednimi argumentami.

generic.Invoke(this, null);
Grax32
źródło
8

Nikt nie podał „ klasycznego rozwiązania refleksji ”, więc oto kompletny przykład kodu:

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;

        }
    }
}

Powyższa DynamicDictionaryFactoryklasa ma metodę

CreateDynamicGenericInstance(Type keyType, Type valueType)

i tworzy i zwraca instancję IDictionary, której typy kluczy i wartości są dokładnie określone w wywołaniu keyTypei valueType.

Oto kompletny przykład, jak wywołać tę metodę, aby utworzyć instancję i użyć 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");
        }
    }
}

Po uruchomieniu powyższej aplikacji konsoli otrzymujemy poprawny, oczekiwany wynik:

Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3
Dimitre Novatchev
źródło
2

To moje 2 centy na podstawie odpowiedzi Graxa , ale z dwiema parametrami wymaganymi dla ogólnej metody.

Załóżmy, że twoja metoda jest zdefiniowana w następujący sposób w klasie Helpers:

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

W moim przypadku typ U jest zawsze obserwowalnym zbiorem przechowującym obiekt typu T.

Ponieważ moje typy są predefiniowane, najpierw tworzę „obojętne” obiekty, które reprezentują obserwowalną kolekcję (U) i obiekt w niej przechowywany (T) i które zostaną użyte poniżej, aby uzyskać ich typ podczas wywoływania Make

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

Następnie wywołaj GetMethod, aby znaleźć swoją funkcję ogólną:

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

Jak dotąd powyższe wywołanie jest prawie identyczne z tym, co wyjaśniono powyżej, ale z niewielką różnicą, gdy trzeba przekazać do niego wiele parametrów.

Musisz przekazać tablicę Type [] do funkcji MakeGenericMethod, która zawiera typy obiektów „obojętnych”, które zostały utworzone powyżej:

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

Gdy to zrobisz, musisz wywołać metodę Invoke, jak wspomniano powyżej.

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

I jesteś skończony. Działa urok!

AKTUALIZACJA:

Jak podkreślono @Bevan, nie muszę tworzyć tablicy podczas wywoływania funkcji MakeGenericMethod, ponieważ przyjmuje ona parametry, i nie muszę tworzyć obiektu, aby uzyskać typy, ponieważ mogę po prostu przekazać typy bezpośrednio do tej funkcji. W moim przypadku, ponieważ mam typy predefiniowane w innej klasie, po prostu zmieniłem kod na:

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 zawiera 2 właściwości typu, Typektóre ustawiam w czasie wykonywania na podstawie wartości wyliczenia przekazywanej do konstruktora i zapewni mi odpowiednie typy, których następnie używam w MakeGenericMethod.

Jeszcze raz dziękuję za wyróżnienie tego @Bevan.

Thierry
źródło
Argumenty, aby MakeGenericMethod()mieć słowo kluczowe params, aby nie trzeba było tworzyć tablicy; nie trzeba też tworzyć instancji, aby uzyskać typy - methodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject))wystarczyłoby.
Bevan
0

Zainspirowany odpowiedzią Enigmativity - załóżmy, że masz dwie (lub więcej) klasy

public class Bar { }
public class Square { }

i chcesz wywołać metodę za Foo<T>pomocą Bari Square, który jest zadeklarowany jako

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

Następnie możesz zaimplementować metodę rozszerzenia, taką jak:

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

Dzięki temu możesz po prostu wywołać Foo:

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

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

który działa dla każdej klasy. W takim przypadku wyświetli:

Kwadratowy
pasek

Matt
źródło