Jak użyć reflection, aby wywołać metodę generyczną?

Jaki jest najlepszy sposób wywołania metody generycznej, gdy parametr type nie jest znany podczas kompilacji,ale jest uzyskiwany dynamicznie w czasie wykonywania?

Rozważ następujący przykładowy kod-wewnątrz metody Example(), Jaki jest najbardziej zwięzły sposób wywołania GenericMethod<T>() za pomocą Type przechowywanej w zmiennej 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>()
    {
        //...
    }
}
Author: Wai Ha Lee, 2008-10-24

7 answers

Musisz użyć reflection, aby rozpocząć metodę, a następnie "skonstruować" ją, dostarczając argumenty typu MakeGenericMethod :

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

W przypadku metody statycznej przekaż null jako pierwszy argument Invoke. To nie ma nic wspólnego z ogólnymi metodami - to po prostu normalne odbicie.

Jak wspomniano, wiele z tego jest prostsze jak w C# 4 używając dynamic - jeśli można użyć typ inference, oczywiście. Nie pomaga w przypadkach, gdy wnioskowanie typu nie jest dostępne, takie jak dokładne przykład w pytaniu.

 948
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-24 09:43:27

Tylko uzupełnienie oryginalnej odpowiedzi. Podczas gdy 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 sprawdzanie czasu kompilacji dla GenericMethod. Jeśli później wykonasz refaktoryzację i zmienisz nazwę GenericMethod, ten kod nie zauważy i nie powiedzie się podczas uruchamiania. Ponadto, jeśli istnieje jakakolwiek obróbka końcowa złożenia (na przykład zaciemnienie lub usunięcie nieużywanych metod/klas), ten kod może również ulec uszkodzeniu.

Więc jeśli znasz metodę, z którą linkujesz w czasie kompilacji, a to nie jest wywołane miliony razy, więc 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);

Choć niezbyt ładny, masz odniesienie do GenericMethod tutaj, a jeśli refaktorujesz, usuniesz lub zrobisz cokolwiek z GenericMethod, ten kod będzie działał, lub przynajmniej pęknie w czasie kompilacji (jeśli na przykład usuniesz GenericMethod).

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

 142
Author: Adrian Gallero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-02 15:06:13

Wywołanie metody generycznej z parametrem typu znanym tylko w trybie runtime można znacznie uprościć za pomocą dynamic wpisz zamiast API reflection.

Aby użyć tej techniki, typ musi być znany z rzeczywistego obiektu (nie tylko instancji klasy Type). W przeciwnym razie musisz utworzyć obiekt tego typu lub użyć standardowego rozwiązania API reflection . Obiekt można utworzyć za pomocą aktywatora .CreateInstance metoda.

Jeśli chcesz wywołać metodę generyczną, która w" normalnym " użyciu miałaby wywnioskowany Typ, to po prostu przychodzi do odlewania obiektu nieznanego typu do 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

Process jest ogólną metodą instancji, która zapisuje rzeczywisty typ przekazanego argumentu (za pomocą metody GetType()) i typ parametru ogólnego (za pomocą operatora typeof).

Rzucając przedmiot argument do typu dynamic odroczyliśmy podanie parametru type do czasu uruchomienia. Gdy metoda Process jest wywołana z argumentem dynamic, kompilator nie dba o Typ tego argumentu. Kompilator generuje kod, który podczas wykonywania sprawdza rzeczywiste typy przekazywanych argumentów (za pomocą reflection) i wybiera najlepszą metodę do wywołania. Tutaj jest tylko jedna metoda generyczna, więc jest wywoływana z odpowiednim parametrem typu.

W tym przykładzie wyjście jest takie samo jak 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 powinieneś także martwić się o wydajność wielokrotnego wywoływania tej funkcji. Kolejne wywołanie z argumentami tego samego typu powinno być szybsze dzięki mechanizmowi buforowania W DLR. Oczywiście możesz napisać kod, który wywołał delegat cache, ale używając typu dynamic otrzymujesz to zachowanie za darmo.

Jeśli metoda generyczna, którą chcesz wywołać, nie ma argumentu parametryzowanego typu (tak więc parametr typu nie może być wywnioskowany), możesz zawinąć wywołanie metody generycznej w metodę pomocniczą, 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

Co jest naprawdę świetne w używaniu dynamic object jako zamiennika dla API reflection jest to, że tracisz tylko czas kompilacji sprawdzania tego konkretnego typu, którego nie znasz do czasu uruchomienia. Inne argumenty i nazwa metody są statycznie analizowane przez kompilator jak zwykle. Jeśli usuniesz lub dodasz więcej argumentów, zmienisz ich typy lub zmienisz nazwę metody, pojawi się błąd w czasie kompilacji. Tak się nie stanie, jeśli podasz nazwę metody jako łańcuch znaków w Type.GetMethod, a argumenty jako tablicę obiektów w MethodInfo.Invoke.

Poniżej znajduje się prosty przykład, który ilustruje, jak niektóre błędy mogą być przechwytywane w czasie kompilacji (skomentowany kod) i inne w czasie wykonywania. Pokazuje również, w jaki sposób DLR próbuje rozwiązać, która metoda ma zostać wywołana.

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 znowu wykonujemy niektóre metoda poprzez oddanie argumentu do typu dynamic. Tylko weryfikacja typu pierwszego argumentu zostanie przełożona do runtime. Pojawi się błąd kompilatora, jeśli nazwa wywołanej metody nie istnieje lub jeśli inne argumenty są nieprawidłowe(Niewłaściwa liczba argumentów lub niewłaściwe typy).

Kiedy przekazujesz argument dynamic do metody, to wywołanie to jest ostatnio powiązane . Metoda rozdzielczość przeciążenia dzieje się w czasie wykonywania i próbuje wybrać najlepsze przeciążenie. Więc jeśli wywołasz ProcessItem metoda z obiektem typu BarItem wtedy w rzeczywistości wywołasz metodę niestandardową, ponieważ jest ona lepiej dopasowana do tego typu. Jednak, gdy podasz argument typu Alpha, pojawi się błąd runtime, ponieważ nie ma metody, która mogłaby obsłużyć ten obiekt (metoda ogólna ma ograniczenie where T : IItem, A klasa Alpha nie implementuje tego interfejsu). Ale o to chodzi. Kompilator nie posiada informacji, że to wywołanie jest poprawne. Ty jako programista wiesz o tym i powinieneś upewnij się, że ten kod działa bez błędów.

Return type

Gdy wywołujesz metodę nie-void z parametrem typu dynamic, jej typ zwracany prawdopodobnie będzie dynamic również . Jeśli więc zmienisz poprzedni przykład na ten kod:

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

Wtedy typem obiektu wynikowego będzie dynamic. Dzieje się tak, ponieważ kompilator nie zawsze wie, która metoda zostanie wywołana. Jeśli znasz typ zwracanego wywołania funkcji, powinieneś domyślnie Konwertuj do wymaganego typu, aby reszta kodu była wpisywana statycznie:

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

Pojawi się błąd runtime, jeśli Typ nie pasuje.

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

 109
Author: Mariusz Pawelski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:27

W C# 4.0, odbicie nie jest konieczne, ponieważ DLR może wywołać go za pomocą typów runtime. Ponieważ korzystanie z biblioteki DLR jest rodzajem bólu dynamicznie (zamiast kompilatora C# generującego kod dla Ciebie), open source framework Dynamitey (. NET standard 1.5) daje Ci łatwy dostęp do tych samych wywołań, które kompilator wygeneruje dla ciebie.

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


var staticContext = InvokeContext.CreateStatic;
Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType}));
 11
Author: jbtule,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-05 22:55:15

Dodaję do odpowiedź Adriana Gallero:

Wywołanie metody generycznej z type info wymaga trzech kroków.

TLDR: wywołanie znanej metody generycznej z obiektem typu może być wykonane przez:

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

Gdzie GenericMethod<object> jest nazwą metody do wywołania i dowolnym typem spełniającym Ogólne Ograniczenia.

(Akcja) pasuje do sygnatury wywołanej metody tj. (Func<string,string,int> lub Action<bool>)

Krok 1 to uzyskanie MethodInfo dla generycznego definicja metody

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

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

Metoda 2: Utwórz delegata, Pobierz obiekt MethodInfo i wywołaj GetGenericMethodDefinition

Z wewnątrz klasy, która zawiera 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 C# nazwa metody, tj. "ToString" lub "GenericMethod" 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, które metoda, o której mówisz.

((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 i GetGenericMethodDefinition

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

To rozpada się na

Utwórz wyrażenie lambda, gdzie ciało to wezwanie do pożądanej metody.

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

Wyodrębnić ciało i oddać do MethodCallExpression

MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;

Get the generic method definition from the method

MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();

Krok 2 to wywołanie MakeGenericMethod, aby utworzyć metodę generyczną o odpowiednim typie (- ach).

MethodInfo generic = method.MakeGenericMethod(myType);

Krok 3 to wywołanie metody z odpowiednimi argumentami.

generic.Invoke(this, null);
 6
Author: Grax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:32

Nikt nie podał rozwiązania " Classic Reflection", 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 klasa DynamicDictionaryFactory 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 keyType i 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");
        }
    }
}

Gdy powyższa konsola aplikacja jest wykonywana, otrzymujemy poprawny, oczekiwany wynik:

Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3
 2
Author: Dimitre Novatchev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-24 01:24:01

To moje 2 centy oparte na odpowiedzi Graxa , ale z dwoma parametrami wymaganymi do metody generycznej.

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ż mam predefiniowane typy, najpierw tworzę "atrapy" obiektów, które reprezentują obserwowalną kolekcję (U) i obiekt w niej przechowywany (T) i które zostaną użyte poniżej, aby uzyskać ich Typ, gdy wywołanie Make

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

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

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

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

Musisz przekazać tablicę typu[] do funkcji MakeGenericMethod, która zawiera typy obiektów "atrapy", 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 jako wymienione powyżej.

generic.Invoke(null, new object[] { csvData });
I gotowe. Działa urok!

UPDATE:

Jak zaznaczono @Bevan, nie muszę tworzyć tablicy podczas wywoływania funkcji MakeGenericMethod, ponieważ zajmuje to params 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 mój 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 Type, ktĂłre ustawiĺ 'em w czasie wykonywania na podstawie wartoĹ" ci enum przekazanej do konstruktora i dostarczy mi odpowiednich typĂłw, ktĂłre potem uĺźywam w MakeGenericMethod.

Jeszcze raz dzięki za podkreślenie tego @Bevan.

 0
Author: Thierry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:48