Frage Wie verwende ich Reflektion, um eine generische Methode aufzurufen?


Wie kann eine generische Methode am besten aufgerufen werden, wenn der Typparameter zum Zeitpunkt der Kompilierung nicht bekannt ist, sondern zur Laufzeit dynamisch abgerufen wird?

Betrachten Sie den folgenden Beispielcode - innerhalb der Example() Methode, was ist die prägnanteste Art zu rufen GenericMethod<T>() Verwendung der Type gespeichert in der myType Variable?

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

844
2017-10-24 05:17


Ursprung


Antworten:


Sie müssen die Methode reflection verwenden, um mit der Methode zu beginnen, und sie dann "konstruieren", indem Sie type arguments mit angeben MakeGenericMethod:

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

Für eine statische Methode übergeben null als erstes Argument zu Invoke. Das hat nichts mit generischen Methoden zu tun - es ist nur eine normale Reflexion.

Wie bereits erwähnt, ist vieles davon einfacher als bei Verwendung von C # 4 dynamic - wenn Sie Typinferenz natürlich verwenden können. Es hilft nicht in Fällen, in denen Typinferenz nicht verfügbar ist, wie das genaue Beispiel in der Frage.


921
2017-10-24 06:13



Nur eine Ergänzung zur ursprünglichen Antwort. Während dies funktioniert:

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

Es ist auch ein wenig gefährlich, dass Sie die Kompilierzeitprüfung verlieren GenericMethod. Wenn Sie später ein Refactoring durchführen und umbenennen GenericMethod, dieser Code wird nicht bemerkt und wird zur Laufzeit fehlschlagen. Wenn es eine Nachbearbeitung der Assembly gibt (z. B. Verschleiern oder Entfernen nicht verwendeter Methoden / Klassen), kann dieser Code ebenfalls beschädigt werden.

Wenn Sie also zum Zeitpunkt der Kompilierung die Methode kennen, zu der Sie verlinken, und dies nicht millionenfach aufgerufen wird, so dass der Aufwand keine Rolle spielt, würde ich diesen Code wie folgt ändern:

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

Obwohl nicht sehr hübsch, haben Sie eine Kompilierzeit Referenz auf GenericMethod hier, und wenn Sie umgestalten, löschen oder etwas damit machen GenericMethod, dieser Code wird weiter funktionieren oder zumindest zur Kompilierzeit unterbrochen (wenn Sie zum Beispiel entfernen GenericMethod).

Eine andere Möglichkeit, dasselbe zu tun, wäre, eine neue Wrapper-Klasse zu erstellen und sie durch zu erstellen Activator. Ich weiß nicht, ob es einen besseren Weg gibt.


135
2018-02-27 16:11



Der Aufruf einer generischen Methode mit einem nur zur Laufzeit bekannten Typparameter kann durch die Verwendung von a erheblich vereinfacht werden dynamic Geben Sie anstelle der Reflektions-API ein.

Um diese Technik zu verwenden, muss der Typ aus dem tatsächlichen Objekt bekannt sein (nicht nur eine Instanz des Type Klasse). Andernfalls müssen Sie ein Objekt dieses Typs erstellen oder die Standardreflexions-API verwenden Lösung. Sie können ein Objekt erstellen, indem Sie den Befehl verwenden Activator.CreateInstance Methode.

Wenn Sie eine generische Methode aufrufen wollen, die in "normaler" Verwendung ihren Typ erhalten hätte, dann wird einfach das Objekt unbekannter Art umgewandelt dynamic. Hier ist ein Beispiel:

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

Und hier ist die Ausgabe dieses Programms:

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 ist eine generische Instanzmethode, die den reellen Typ des übergebenen Arguments schreibt (mithilfe von GetType() Methode) und der Typ des generischen Parameters (mit typeof Operator).

Durch Übergeben des Objektarguments an dynamic Geben Sie den Typ zurück, indem Sie den Typparameter bis zur Laufzeit angeben. Wenn das Process Methode wird mit dem aufgerufen dynamic Argument dann kümmert sich der Compiler nicht um den Typ dieses Arguments. Der Compiler generiert Code, der zur Laufzeit die reellen Typen von übergebenen Argumenten überprüft (durch Verwendung von Reflektion) und die beste Methode zum Aufruf auswählt. Hier gibt es nur diese eine generische Methode, also wird sie mit einem richtigen Typparameter aufgerufen.

In diesem Beispiel ist die Ausgabe die gleiche wie wenn Sie geschrieben haben:

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

Die Version mit einem dynamischen Typ ist definitiv kürzer und einfacher zu schreiben. Sie sollten sich auch nicht darum sorgen, dass diese Funktion mehrmals aufgerufen wird. Der nächste Aufruf mit Argumenten des gleichen Typs sollte schneller sein dank der Zwischenspeichern Mechanismus im DLR. Natürlich können Sie Code schreiben, der aufgerufene Delegierte im Cache speichert, aber mithilfe von dynamic Geben Sie dieses Verhalten kostenlos ein.

Wenn die generische Methode, die Sie aufrufen möchten, kein Argument eines parametrisierten Typs enthält (daher kann ihr Typparameter nicht abgeleitet werden), können Sie den Aufruf der generischen Methode in eine Hilfsmethode wie im folgenden Beispiel einschließen:

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

Erhöhte Art Sicherheit

Was ist wirklich toll an der Verwendung? dynamic Objekt als Ersatz für die Verwendung von Reflection-API ist, dass Sie nur die Kompilierzeitüberprüfung dieses bestimmten Typs verlieren, die Sie bis zur Laufzeit nicht kennen. Andere Argumente und der Name der Methode werden vom Compiler wie gewohnt statisch analysiert. Wenn Sie weitere Argumente entfernen oder hinzufügen, ihre Typen ändern oder den Methodennamen umbenennen, erhalten Sie einen Kompilierungsfehler. Dies geschieht nicht, wenn Sie den Methodennamen als Zeichenfolge angeben Type.GetMethod und Argumente als das Objekt-Array in MethodInfo.Invoke.

Im Folgenden sehen Sie ein einfaches Beispiel, das veranschaulicht, wie einige Fehler zur Kompilierungszeit (kommentierter Code) und andere zur Laufzeit abgefangen werden können. Es zeigt auch, wie der DLR versucht, aufzulösen, welche Methode aufgerufen werden soll.

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

Hier führen wir erneut eine Methode aus, indem wir das Argument auf die dynamic Art. Nur die Überprüfung des ersten Argumenttyps wird auf die Laufzeit verschoben. Sie erhalten einen Compilerfehler, wenn der Name der Methode, die Sie aufrufen, nicht existiert oder wenn andere Argumente ungültig sind (falsche Anzahl von Argumenten oder falsche Typen).

Wenn du an der dynamic Argument zu einer Methode dann ist dieser Aufruf kürzlich gebunden. Die Überladung der Methoden geschieht zur Laufzeit und versucht die beste Überladung zu wählen. Also, wenn Sie das aufrufen ProcessItem Methode mit einem Objekt von BarItem Geben Sie dann die nicht generische Methode ein, weil sie besser zu diesem Typ passt. Sie erhalten jedoch einen Laufzeitfehler, wenn Sie ein Argument von der übergeben Alpha Typ, weil es keine Methode gibt, die mit diesem Objekt umgehen kann (eine generische Methode hat die Einschränkung) where T : IItem und Alpha Klasse implementiert diese Schnittstelle nicht). Aber das ist der springende Punkt. Der Compiler hat keine Informationen, dass dieser Aufruf gültig ist. Sie als Programmierer wissen das, und Sie sollten sicherstellen, dass dieser Code ohne Fehler ausgeführt wird.

Rückgabetyp Gotcha

Wenn Sie eine nicht-void -Methode mit einem Parameter des dynamischen Typs aufrufen, wird der Rückgabetyp wahrscheinlich Sein dynamic auch. Wenn Sie das vorherige Beispiel für diesen Code ändern würden:

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

dann wäre der Typ des Ergebnisobjekts dynamic. Dies liegt daran, dass der Compiler nicht immer weiß, welche Methode aufgerufen wird. Wenn Sie den Rückgabetyp des Funktionsaufrufs kennen, sollten Sie dies tun implizit konvertieren es auf den erforderlichen Typ, so dass der Rest des Codes statisch getippt wird:

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

Sie erhalten einen Laufzeitfehler, wenn der Typ nicht übereinstimmt.

Wenn Sie im vorherigen Beispiel versuchen, den Ergebniswert zu erhalten, erhalten Sie in der zweiten Schleifeniteration einen Laufzeitfehler. Dies liegt daran, dass Sie versucht haben, den Rückgabewert einer void-Funktion zu speichern.


105
2018-03-16 19:21



Mit C # 4.0 ist eine Reflektion nicht erforderlich, da die DLR sie mit Laufzeittypen aufrufen kann. Seit der Verwendung der DLR-Bibliothek ist eine Art von Schmerz dynamisch (anstelle des C # -Compilers, der Code für Sie generiert), das Open-Source-Framework Dynamite (.net Standard 1.5) gibt Ihnen einfachen Cache-Laufzeit-Zugriff auf die gleichen Aufrufe, die der Compiler für Sie generieren würde.

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
2017-07-05 13:40



Hinzufügen zu Adrian Galleros Antwort:

Der Aufruf einer generischen Methode aus dem Typ info erfolgt in drei Schritten.

TLDR: Der Aufruf einer bekannten generischen Methode mit einem Typobjekt kann durchgeführt werden durch:

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

woher GenericMethod<object> ist der aufzurufende Methodenname und jeder Typ, der die generischen Einschränkungen erfüllt.

(Aktion) stimmt mit der Signatur der anzurufenden Methode überein, d.h.Func<string,string,int> oder Action<bool>)

Schritt 1 ruft die MethodInfo für die generische Methodendefinition ab

Methode 1: Verwenden Sie GetMethod () oder GetMethods () mit geeigneten Typen oder Bindungsflags.

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

Methode 2: Erstellen Sie einen Delegaten, rufen Sie das MethodInfo-Objekt ab, und rufen Sie dann GetGenericMethodDefinition auf

Von innerhalb der Klasse, die die Methoden enthält:

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

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

Von außerhalb der Klasse, die die Methoden enthält:

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

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

In C # bezieht sich der Name einer Methode, d. H. "ToString" oder "GenericMethod", auf eine Gruppe von Methoden, die eine oder mehrere Methoden enthalten können. Bis Sie die Typen der Methodenparameter angeben, ist nicht bekannt, welche Methode, auf die Sie sich beziehen.

((Action)GenericMethod<object>) verweist auf den Delegaten für eine bestimmte Methode. ((Func<string, int>)GenericMethod<object>) bezieht sich auf eine andere Überladung von GenericMethod

Methode 3: Erstellen Sie einen Lambda-Ausdruck, der einen Methodenaufrufausdruck enthält, rufen Sie das MethodInfo-Objekt und dann GetGenericMethodDefinition ab

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

Dies ergibt sich zu

Erstellen Sie einen Lambda-Ausdruck, bei dem der Körper ein Aufruf an Ihre gewünschte Methode ist.

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

Extrahieren Sie den Körper und werfen Sie ihn nach MethodCallExpression

MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;

Ruft die generische Methodendefinition aus der Methode ab

MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();

Schritt 2 ruft MakeGenericMethod auf, um eine generische Methode mit den entsprechenden Typen zu erstellen.

MethodInfo generic = method.MakeGenericMethod(myType);

Schritt 3 ruft die Methode mit den entsprechenden Argumenten auf.

generic.Invoke(this, null);

5
2018-01-09 22:20



Niemand hat die "klassische Reflexion"Lösung, also hier ist ein komplettes Codebeispiel:

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;

        }
    }
}

Obenstehendes DynamicDictionaryFactory Klasse hat eine Methode

CreateDynamicGenericInstance(Type keyType, Type valueType)

und es erstellt eine IDictionary-Instanz und gibt sie zurück, wobei die Typen der Schlüssel und Werte genau die für den Aufruf angegebenen sind keyType und valueType.

Hier ist ein komplettes Beispiel wie diese Methode aufgerufen wird, um eine Instanz zu instanziieren und zu verwenden 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");
        }
    }
}

Wenn die obige Konsolenanwendung ausgeführt wird, erhalten wir das korrekte, erwartete Ergebnis:

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

2
2017-08-24 01:24



Dies ist meine 2 Cent basierend auf Grax 'Antwort, aber mit zwei Parametern, die für eine generische Methode benötigt werden.

Angenommen, Ihre Methode ist in einer Helpers-Klasse wie folgt definiert:

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

In meinem Fall ist der U-Typ immer eine beobachtbare Sammlung, die ein Objekt vom Typ T speichert.

Da ich meine Typen vordefiniert habe, erstelle ich zuerst die "Dummy" -Objekte, die die beobachtbare Sammlung (U) und das darin gespeicherte Objekt (T) darstellen und die unten verwendet werden, um ihren Typ beim Aufruf des Make zu erhalten

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

Rufen Sie dann GetMethod auf, um Ihre generische Funktion zu finden:

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

Bis jetzt ist der obige Aufruf ziemlich identisch mit dem, was oben erklärt wurde, aber mit einem kleinen Unterschied, wenn Sie mehrere Parameter an ihn übergeben müssen.

Sie müssen ein Type [] - Array an die MakeGenericMethod-Funktion übergeben, die die oben erstellten "Dummy" -Objekttypen enthält:

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

Sobald dies erledigt ist, müssen Sie die Invoke-Methode wie oben erwähnt aufrufen.

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

Und du bist fertig. Funktioniert ein Charme!

AKTUALISIEREN:

Wie @Bevan hervorhebt, brauche ich beim Aufruf der MakeGenericMethod-Funktion kein Array zu erstellen, da es Parameter aufnimmt und ich brauche kein Objekt zu erstellen, um die Typen zu erhalten, da ich die Typen direkt an diese Funktion übergeben kann. In meinem Fall, da ich die Typen in einer anderen Klasse vordefiniert habe, habe ich einfach meinen Code geändert in:

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 enthält 2 Eigenschaften des Typs Type die ich zur Laufzeit basierend auf einem Enum-Wert gesetzt habe, der an den Konstruktor übergeben wurde und mir die relevanten Typen zur Verfügung stellt, die ich dann in der MakeGenericMethod verwende.

Nochmals vielen Dank für die Hervorhebung dieses @Bevan.


0
2017-10-22 23:28