1300 Stimmen

Wie verwende ich Reflection, um eine generische Methode aufzurufen?

Wie ruft man eine generische Methode am besten auf, wenn der Typparameter nicht zur Kompilierzeit bekannt ist, sondern erst zur Laufzeit dynamisch ermittelt wird?

Betrachten Sie den folgenden Beispielcode - innerhalb der Example() Methode aufzurufen, was ist der prägnanteste Weg, um die GenericMethod<T>() unter Verwendung der Type gespeichert in der myType variabel?

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

9 Stimmen

Ich habe Jons Lösung ausprobiert und konnte sie nicht zum Laufen bringen, bis ich die generische Methode in meiner Klasse öffentlich gemacht habe. Ich weiß, dass ein anderer Jon geantwortet hat, dass Sie die Bindungsflags angeben müssen, aber das hat nicht geholfen.

14 Stimmen

Sie benötigen außerdem BindingFlags.Instance , nicht nur BindingFlags.NonPublic , um die private/interne Methode zu erhalten.

2 Stimmen

Die moderne Version dieser Frage: stackoverflow.com/q/2433436/103167

1361voto

Jon Skeet Punkte 1325502

Sie müssen Reflection verwenden, um die Methode zu erhalten, und sie dann "konstruieren", indem Sie Typargumente mit MakeGenericMethod :

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

Für eine statische Methode, übergeben Sie null als erstes Argument für Invoke . Das hat nichts mit generischen Methoden zu tun, sondern ist eine ganz normale Reflexion.

Wie bereits erwähnt, ist vieles davon ab C# 4 einfacher geworden, indem man dynamic - natürlich nur, wenn Sie Typinferenz verwenden können. In Fällen, in denen keine Typinferenz möglich ist, wie z. B. in dem in der Frage genannten Beispiel, ist dies nicht hilfreich.

119 Stimmen

+1; beachten Sie, dass GetMethod() berücksichtigt standardmäßig nur öffentliche Instanzmethoden, daher müssen Sie möglicherweise BindingFlags.Static und/oder BindingFlags.NonPublic .

30 Stimmen

Die richtige Kombination von Flaggen ist BindingFlags.NonPublic | BindingFlags.Instance (und optional BindingFlags.Static ).

0 Stimmen

@LarsKemmann: Warum NonPublic wenn die gewünschte Methode öffentlich ist?

195voto

Adrian Gallero Punkte 1869

Nur eine Ergänzung zur ursprünglichen Antwort. Dies wird zwar funktionieren:

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

Es ist auch ein wenig gefährlich, da Sie die Kompilierzeitprüfung für GenericMethod . Wenn Sie später ein Refactoring durchführen und den Namen GenericMethod wird dieser Code nicht bemerken und zur Laufzeit fehlschlagen. Auch bei einer Nachbearbeitung der Baugruppe (z. B. Verschleierung oder Entfernung nicht verwendeter Methoden/Klassen) kann dieser Code fehlschlagen.

Wenn Sie also die Methode kennen, auf die Sie zur Kompilierzeit verlinken, und diese nicht millionenfach aufgerufen wird, so dass der Overhead keine Rolle spielt, würde ich diesen Code so ä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);

Das ist zwar nicht sehr hübsch, aber Sie haben einen Kompilierzeitverweis auf GenericMethod hier, und wenn Sie etwas umstrukturieren, löschen oder mit GenericMethod zu kompilieren, wird dieser Code weiterhin funktionieren oder zumindest bei der Kompilierung abbrechen (wenn Sie zum Beispiel die GenericMethod ).

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

6 Stimmen

In Fällen, in denen Reflexion zum Aufrufen einer Methode verwendet wird, ist es üblich, dass der Methodenname selbst von einer anderen Methode entdeckt wird. Es ist nicht üblich, den Namen der Methode im Voraus zu kennen.

16 Stimmen

Nun, ich stimme zu, wenn es um die allgemeine Verwendung von Reflexion geht. Aber die ursprüngliche Frage war, wie man "GenericMethod<myType>()" aufruft. Wenn diese Syntax erlaubt wäre, bräuchten wir GetMethod() überhaupt nicht. Aber für die Frage "wie schreibe ich "GenericMethod<myType>"? Ich denke, die Antwort sollte einen Weg beinhalten, den Verlust der Kompilierzeitverknüpfung mit GenericMethod zu vermeiden. Nun, ob diese Frage üblich ist oder nicht, weiß ich nicht, aber ich weiß, dass ich gestern genau dieses Problem hatte, und deshalb bin ich bei dieser Frage gelandet.

20 Stimmen

Sie könnten Folgendes tun GenMethod.Method.GetGenericMethodDefinition() anstelle von this.GetType().GetMethod(GenMethod.Method.Name) . Es ist etwas sauberer und wahrscheinlich sicherer.

183voto

Mariusz Pawelski Punkte 21614

Der Aufruf einer generischen Methode mit einem Typparameter, der nur zur Laufzeit bekannt ist, kann stark vereinfacht werden, indem man eine dynamic Typ anstelle der Reflection-API.

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

Wenn Sie eine generische Methode aufrufen wollen, deren Typ bei "normaler" Verwendung abgeleitet worden wäre, dann kommt es einfach darauf an, das Objekt unbekannten Typs nach 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 realen Typ des übergebenen Arguments schreibt (durch Verwendung der GetType() Methode) und den Typ des generischen Parameters (durch Verwendung von typeof Operator).

Durch Casting des Objektarguments an dynamic Typ haben wir die Angabe des Typparameters auf die Laufzeit verschoben. Wenn der Process Methode wird mit der dynamic Argument, dann ist es dem Compiler egal, welchen Typ dieses Argument hat. Der Compiler erzeugt einen Code, der zur Laufzeit die tatsächlichen Typen der übergebenen Argumente prüft (unter Verwendung von Reflexion) und die beste aufzurufende Methode auswählt. Hier gibt es nur diese eine generische Methode, die mit einem geeigneten Typ-Parameter aufgerufen wird.

In diesem Beispiel ist die Ausgabe die gleiche, wie wenn Sie schreiben würden:

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 keine Sorgen um die Leistung machen, wenn Sie diese Funktion mehrmals aufrufen. Der nächste Aufruf mit Argumenten desselben Typs sollte schneller sein, dank der Caching Mechanismus im DLR. Natürlich können Sie Code schreiben, der aufgerufene Delegierte zwischenspeichert, aber durch die Verwendung der dynamic Typ erhalten Sie dieses Verhalten kostenlos.

Wenn die aufzurufende generische Methode kein Argument eines parametrisierten Typs hat (so dass ihr Typparameter nicht abgeleitet werden kann), dann können Sie den Aufruf der generischen Methode in eine Hilfsmethode wie im folgenden Beispiel verpacken:

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 Typensicherheit

Das wirklich Tolle an der Verwendung von dynamic Objekts als Ersatz für die Verwendung der Reflection-API ist, dass Sie nur die Kompilierzeitprüfung dieses bestimmten Typs verlieren, den Sie erst zur Laufzeit kennen. Die anderen Argumente und der Name der Methode werden vom Compiler wie üblich statisch analysiert. Wenn Sie weitere Argumente entfernen oder hinzufügen, ihre Typen ändern oder den Namen der Methode umbenennen, erhalten Sie einen Kompilierzeitfehler. Dies wird nicht passieren, wenn Sie den Methodennamen als String in Type.GetMethod und Argumente wie das Array der Objekte in MethodInfo.Invoke .

Das folgende einfache Beispiel veranschaulicht, wie einige Fehler zur Kompilierzeit (kommentierter Code) und andere zur Laufzeit abgefangen werden können. Es zeigt auch, wie das DLR versucht zu entscheiden, 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 wieder eine Methode aus, indem wir das Argument an die dynamic Typ. Nur die Überprüfung des Typs des ersten Arguments 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 Sie die dynamic Argument an eine Methode, dann ist dieser Aufruf in letzter Zeit gebunden . Die Auflösung von Methodenüberladungen erfolgt zur Laufzeit und versucht, die beste Überladung zu wählen. Wenn Sie also die Methode ProcessItem Methode mit einem Objekt von BarItem dann rufen Sie tatsächlich die nicht-generische Methode auf, weil sie besser zu diesem Typ passt. Sie werden jedoch einen Laufzeitfehler erhalten, wenn Sie ein Argument des Typs Alpha Typ, weil es keine Methode gibt, die dieses Objekt behandeln kann (eine generische Methode hat die Einschränkung where T : IItem y Alpha Klasse implementiert diese Schnittstelle nicht). Aber genau das ist der Punkt. Der Compiler hat keine Information, dass dieser Aufruf gültig ist. Sie als Programmierer wissen das, und Sie sollten dafür sorgen, dass dieser Code fehlerfrei läuft.

Rückgabetyp gotcha

Wenn Sie eine nicht-voide Methode mit einem Parameter dynamischen Typs aufrufen, wird der Rückgabetyp wahrscheinlich sein dynamic zu . Wenn Sie also das vorherige Beispiel in diesen Code ändern würden:

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

dann wäre der Typ des Ergebnisobjekts dynamic . Der Grund dafür ist, dass der Compiler nicht immer weiß, welche Methode aufgerufen werden soll. Wenn Sie den Rückgabetyp des Funktionsaufrufs kennen, sollten Sie implizit konvertieren in den erforderlichen Typ umwandeln, damit der Rest des Codes statisch typisiert ist:

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

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

Wenn Sie versuchen, den Ergebniswert aus dem vorherigen Beispiel zu ermitteln, erhalten Sie in der zweiten Schleifeniteration einen Laufzeitfehler. Das liegt daran, dass Sie versucht haben, den Rückgabewert einer ungültigen Funktion zu speichern.

0 Stimmen

Mariusz, verwirrt durch "Sie erhalten jedoch einen Laufzeitfehler, wenn Sie ein Argument vom Typ Alpha übergeben, da es keine Methode gibt, die dieses Objekt verarbeiten kann. "Wenn ich var a = new Alpha() ProcessItem(a, "test" + i, i) aufrufe, warum würde die generische ProcessItem-Methode dies nicht effektiv behandeln und "General Process Item" ausgeben?

1 Stimmen

@AlexEdelstein Ich habe meine Antwort überarbeitet, um sie ein wenig zu verdeutlichen. Es ist, weil generische ProcessItem Methode hat eine generische Einschränkung und akzeptiert nur Objekte, die die IItem Schnittstelle. Wenn Sie die ProcessItem(new Aplha(), "test" , 1); ou ProcessItem((object)(new Aplha()), "test" , 1); erhalten Sie einen Compilerfehler, aber beim Casting nach dynamic verschieben Sie diese Prüfung auf die Laufzeit.

0 Stimmen

Tolle Antwort und Erklärung, funktioniert perfekt für mich. Viel besser als die akzeptierte Antwort, kürzer zu schreiben, leistungsfähiger und sicherer.

24voto

Grax32 Punkte 3763

Hinzufügung zu Adrian Galleros Antwort :

Der Aufruf einer generischen Methode vom Typ info umfasst drei Schritte.

TLDR: Der Aufruf einer bekannten generischen Methode mit einem Typobjekt kann erreicht werden durch:

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

donde GenericMethod<object> ist der Name der aufzurufenden Methode und ein beliebiger Typ, der den generischen Beschränkungen genügt.

(Action) mit der Signatur der aufzurufenden Methode übereinstimmt, d.h. ( Func<string,string,int> o Action<bool> )

Schritt 1 ist die Ermittlung der MethodInfo für die generische Methodendefinition

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

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

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

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, z. B. "ToString" oder "GenericMethod", eigentlich auf eine Gruppe von Methoden, die eine oder mehrere Methoden enthalten kann. Bis Sie die Typen der Methodenparameter angeben, ist nicht bekannt, welche Methode Sie sich beziehen.

((Action)GenericMethod<object>) verweist auf den Delegierten 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 Methodenaufruf enthält, holen Sie das MethodInfo-Objekt und dann GetGenericMethodDefinition

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

Dies setzt sich wie folgt zusammen

Erstellen Sie einen Lambda-Ausdruck, dessen Körper ein Aufruf der gewünschten Methode ist.

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

Extrahieren des Körpers und Umwandlung in MethodCallExpression

MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;

Abrufen der generischen Methodendefinition aus der Methode

MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();

Schritt 2 ist der Aufruf von MakeGenericMethod, um eine generische Methode mit dem/den entsprechenden Typ(en) zu erstellen.

MethodInfo generic = method.MakeGenericMethod(myType);

Schritt 3 ist der Aufruf der Methode mit den entsprechenden Argumenten.

generic.Invoke(this, null);

19voto

jbtule Punkte 30716

Mit C# 4.0 ist Reflection nicht notwendig, da das DLR es über Laufzeittypen aufrufen kann. Da die Verwendung der DLR-Bibliothek dynamisch ziemlich mühsam ist (anstatt dass der C#-Compiler Code für Sie generiert), wurde das Open-Source-Framework Dynamitey (.net standard 1.5) ermöglicht Ihnen einen einfachen, zwischengespeicherten Laufzeitzugriff auf die gleichen Aufrufe, die der Compiler für Sie erzeugen 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}));

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X