Wie kann eine generische Methode am besten aufgerufen werden, wenn der Typparameter zur Kompilierungszeit nicht bekannt ist, sondern zur Laufzeit dynamisch abgerufen wird?
Betrachten Sie den folgenden Beispielcode Example()
: Was ist innerhalb der Methode die prägnanteste Methode zum Aufrufen GenericMethod<T>()
mit dem Type
in der myType
Variablen gespeicherten Code ?
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>()
{
//...
}
}
c#
.net
generics
reflection
Bevan
quelle
quelle
BindingFlags.Instance
auch nicht nurBindingFlags.NonPublic
die private / interne Methode erhalten.Antworten:
Sie müssen Reflection verwenden, um die Methode zu starten, und sie dann "konstruieren", indem Sie Typargumente mit MakeGenericMethod angeben :
Übergeben Sie für eine statische Methode
null
als erstes Argument anInvoke
. Das hat nichts mit generischen Methoden zu tun - es ist nur normale Reflexion.Wie bereits erwähnt, ist vieles davon ab C # 4 einfacher
dynamic
- wenn Sie die Typinferenz verwenden können, natürlich. Es hilft nicht in Fällen, in denen keine Typinferenz verfügbar ist, wie im genauen Beispiel in der Frage.quelle
GetMethod()
standardmäßig nur öffentliche Instanzmethoden berücksichtigt werden, sodass Sie möglicherweiseBindingFlags.Static
und / oder benötigenBindingFlags.NonPublic
.BindingFlags.NonPublic | BindingFlags.Instance
(und optionalBindingFlags.Static
).dynamic
nicht hilft , weil Typinferenz nicht verfügbar ist. (Es gibt keine Argumente, mit denen der Compiler das Typargument bestimmen kann.)Nur eine Ergänzung zur ursprünglichen Antwort. Während dies funktionieren wird:
Es ist auch ein wenig gefährlich, dass Sie die Überprüfung der Kompilierungszeit für verlieren
GenericMethod
. Wenn Sie später ein Refactoring durchführen und umbenennenGenericMethod
, wird dieser Code nicht bemerkt und schlägt zur Laufzeit fehl. Wenn die Assembly nachbearbeitet wird (z. B. nicht verwendete Methoden / Klassen verschleiern oder entfernen), kann dieser Code ebenfalls beschädigt werden.Wenn Sie also die Methode kennen, mit der Sie zur Kompilierungszeit verknüpfen, und diese nicht millionenfach aufgerufen wird, sodass der Overhead keine Rolle spielt, würde ich diesen Code folgendermaßen ändern:
Obwohl dies nicht sehr hübsch ist, haben Sie hier einen Verweis auf die Kompilierungszeit.
GenericMethod
Wenn Sie denGenericMethod
Code umgestalten, löschen oder etwas damit tun , funktioniert er weiter oder bricht zumindest zur Kompilierungszeit ab (wenn Sie ihn beispielsweise entfernenGenericMethod
).Eine andere Möglichkeit, dasselbe zu tun, besteht darin, eine neue Wrapper-Klasse zu erstellen und diese zu erstellen
Activator
. Ich weiß nicht, ob es einen besseren Weg gibt.quelle
GenMethod.Method.GetGenericMethodDefinition()
stattthis.GetType().GetMethod(GenMethod.Method.Name)
. Es ist etwas sauberer und wahrscheinlich sicherer.nameof(GenericMethod)
Das Aufrufen einer generischen Methode mit einem nur zur Laufzeit bekannten Typparameter kann durch die Verwendung eines
dynamic
Typs anstelle der Reflection-API erheblich vereinfacht werden .Um diese Technik verwenden zu können, muss der Typ aus dem tatsächlichen Objekt bekannt sein (nicht nur aus einer Instanz der
Type
Klasse). Andernfalls müssen Sie ein Objekt dieses Typs erstellen oder die Standard-Reflection-API- Lösung verwenden . Sie können ein Objekt mithilfe der Activator.CreateInstance- Methode erstellen .Wenn Sie eine generische Methode aufrufen möchten, deren Typ bei "normaler" Verwendung abgeleitet worden wäre, wird einfach das Objekt eines unbekannten Typs in umgewandelt
dynamic
. Hier ist ein Beispiel:Und hier ist die Ausgabe dieses Programms:
Process
ist eine generische Instanzmethode, die den realen Typ des übergebenen Arguments (mithilfe derGetType()
Methode) und den Typ des generischen Parameters (mithilfe destypeof
Operators) schreibt .Durch Umwandeln des Objektarguments in
dynamic
Typ haben wir die Bereitstellung des Typparameters bis zur Laufzeit verschoben. Wenn dieProcess
Methode mit demdynamic
Argument aufgerufen wird, kümmert sich der Compiler nicht um den Typ dieses Arguments. Der Compiler generiert Code, der zur Laufzeit die tatsächlichen Typen der übergebenen Argumente überprüft (mithilfe von Reflection) und die beste aufzurufende Methode auswählt. Hier gibt es nur diese eine generische Methode, daher wird sie mit einem geeigneten Typparameter aufgerufen.In diesem Beispiel ist die Ausgabe dieselbe, als ob Sie geschrieben hätten:
Die Version mit einem dynamischen Typ ist definitiv kürzer und einfacher zu schreiben. Sie sollten sich auch keine Gedanken über die Leistung machen, wenn Sie diese Funktion mehrmals aufrufen. Der nächste Aufruf mit Argumenten des gleichen Typs sollte dank des Caching- Mechanismus im DLR schneller sein . Natürlich können Sie Code schreiben, der aufgerufene Delegaten zwischenspeichert, aber wenn Sie den
dynamic
Typ verwenden, erhalten Sie dieses Verhalten kostenlos.Wenn die generische Methode, die Sie aufrufen möchten, kein Argument eines parametrisierten Typs enthält (sodass der Typparameter nicht abgeleitet werden kann), können Sie den Aufruf der generischen Methode in eine Hilfsmethode wie im folgenden Beispiel einschließen:
Erhöhte Typensicherheit
Das
dynamic
Besondere an der Verwendung von Objekten als Ersatz für die Verwendung der Reflection-API ist, dass Sie nur die Überprüfung der Kompilierungszeit dieses bestimmten Typs verlieren, die Sie erst zur Laufzeit kennen. Andere Argumente und der Name der Methode werden vom Compiler wie gewohnt statisch analysiert. Wenn Sie weitere Argumente entfernen oder hinzufügen, deren Typen ändern oder den Methodennamen umbenennen, wird ein Fehler beim Kompilieren angezeigt. Dies ist nicht der Fall, wenn Sie den Methodennamen als ZeichenfolgeType.GetMethod
und Argumente als Objektarray angebenMethodInfo.Invoke
.Im Folgenden finden Sie ein einfaches Beispiel, das zeigt, wie einige Fehler zur Kompilierungszeit (kommentierter Code) und andere zur Laufzeit abgefangen werden können. Es zeigt auch, wie das DLR versucht, die aufzurufende Methode aufzulösen.
Hier führen wir erneut eine Methode aus, indem wir das Argument in den
dynamic
Typ umwandeln. Nur die Überprüfung des Typs des ersten Arguments wird auf die Laufzeit verschoben. Sie erhalten einen Compilerfehler, wenn der Name der von Ihnen aufgerufenen Methode nicht vorhanden ist oder wenn andere Argumente ungültig sind (falsche Anzahl von Argumenten oder falsche Typen).Wenn Sie das
dynamic
Argument an eine Methode übergeben, ist dieser Aufruf kürzlich gebunden . Die Auflösung der Methodenüberladung erfolgt zur Laufzeit und versucht, die beste Überladung auszuwählen. Wenn Sie also dieProcessItem
Methode mit einem Objekt vomBarItem
Typ aufrufen, rufen Sie die nicht generische Methode auf, da sie besser zu diesem Typ passt. Wenn Sie ein Argument desAlpha
Typs übergeben , wird jedoch ein Laufzeitfehler angezeigt, da es keine Methode gibt, die dieses Objekt verarbeiten kann (eine generische Methode hat die Einschränkungwhere T : IItem
und dieAlpha
Klasse implementiert diese Schnittstelle nicht). Aber das ist der springende Punkt. Der Compiler hat keine Informationen darüber, dass dieser Aufruf gültig ist. Sie als Programmierer wissen dies und sollten sicherstellen, dass dieser Code fehlerfrei ausgeführt wird.Rückgabetyp gotcha
Wenn Sie eine nicht-void - Methode mit einem Parameter der dynamischen Art Aufruf, wird seine Rückkehr Art wahrscheinlich sein
dynamic
zu . Wenn Sie also das vorherige Beispiel in diesen Code ändern würden: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 ihn implizit in den erforderlichen Typ konvertieren , damit der Rest des Codes statisch typisiert wird:Sie erhalten einen Laufzeitfehler, wenn der Typ nicht übereinstimmt.
Wenn Sie versuchen, den Ergebniswert im vorherigen Beispiel abzurufen, wird in der zweiten Schleifeniteration ein Laufzeitfehler angezeigt. Dies liegt daran, dass Sie versucht haben, den Rückgabewert einer void-Funktion zu speichern.
quelle
ProcessItem
Methode generische Einschränkungen aufweist und nur Objekte akzeptiert, die dieIItem
Schnittstelle implementieren . Wenn Sie anrufenProcessItem(new Aplha(), "test" , 1);
oderProcessItem((object)(new Aplha()), "test" , 1);
eine Compiler-Fehlermeldung erhalten, aber beim Castingdynamic
die Überprüfung auf Laufzeit verschieben.Mit C # 4.0 ist keine Reflexion erforderlich, da das DLR sie mithilfe von Laufzeittypen aufrufen kann. Da die Verwendung der DLR-Bibliothek dynamisch schwierig ist (anstelle des C # -Compilers, der Code für Sie generiert), bietet Ihnen das Open-Source-Framework Dynamitey (.net Standard 1.5) einen einfachen zwischengespeicherten Laufzeitzugriff auf dieselben Aufrufe, die der Compiler generieren würde für dich.
quelle
Hinzu kommt die Antwort von Adrian Gallero :
Das Aufrufen einer generischen Methode aus Typinformationen umfasst drei Schritte.
TLDR: Das Aufrufen einer bekannten generischen Methode mit einem Typobjekt kann folgendermaßen erfolgen:
Wo
GenericMethod<object>
ist der aufzurufende Methodenname und jeder Typ, der die allgemeinen Einschränkungen erfüllt?(Aktion) entspricht der Signatur der aufzurufenden Methode, dh (
Func<string,string,int>
oderAction<bool>
)In Schritt 1 wird die MethodInfo für die generische Methodendefinition abgerufen
Methode 1: Verwenden Sie GetMethod () oder GetMethods () mit geeigneten Typen oder Bindungsflags.
Methode 2: Erstellen Sie einen Delegaten, rufen Sie das MethodInfo-Objekt ab und rufen Sie GetGenericMethodDefinition auf
Aus der Klasse heraus, die die Methoden enthält:
Von außerhalb der Klasse, die die Methoden enthält:
In C # bezieht sich der Name einer Methode, dh "ToString" oder "GenericMethod", tatsächlich auf eine Gruppe von Methoden, die eine oder mehrere Methoden enthalten können. Bis Sie die Typen der Methodenparameter angeben, ist nicht bekannt, auf welche Methode Sie sich beziehen.
((Action)GenericMethod<object>)
bezieht sich auf den Delegaten für eine bestimmte Methode.((Func<string, int>)GenericMethod<object>)
verweist auf eine andere Überladung von GenericMethodMethode 3: Erstellen Sie einen Lambda-Ausdruck, der einen Methodenaufrufausdruck enthält, rufen Sie das MethodInfo-Objekt und dann GetGenericMethodDefinition ab
Das bricht zusammen zu
Erstellen Sie einen Lambda-Ausdruck, bei dem der Körper die gewünschte Methode aufruft.
Extrahieren Sie den Body und wandeln Sie ihn in MethodCallExpression um
Rufen Sie die generische Methodendefinition aus der Methode ab
Schritt 2 ruft MakeGenericMethod auf, um eine generische Methode mit den entsprechenden Typen zu erstellen.
Schritt 3 ruft die Methode mit den entsprechenden Argumenten auf.
quelle
Niemand hat die " klassische Reflection " -Lösung bereitgestellt. Hier ist ein vollständiges Codebeispiel:
Die obige
DynamicDictionaryFactory
Klasse hat eine MethodeCreateDynamicGenericInstance(Type keyType, Type valueType)
und es erstellt und gibt eine IDictionary-Instanz zurück, deren Schlüssel und Werte genau den im Aufruf
keyType
und angegebenen Werten entsprechenvalueType
.Hier ist ein vollständiges Beispiel, wie diese Methode aufgerufen wird, um Folgendes zu instanziieren und zu verwenden
Dictionary<String, int>
:Wenn die obige Konsolenanwendung ausgeführt wird, erhalten wir das richtige, erwartete Ergebnis:
quelle
Dies sind meine 2 Cent basierend auf Grax 'Antwort , aber mit zwei Parametern, die für eine generische Methode erforderlich sind.
Angenommen, Ihre Methode ist in einer Helpers-Klasse wie folgt definiert:
In meinem Fall ist der Typ U immer ein beobachtbares Sammlungsobjekt vom Typ T.
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 Aufrufen von Make abzurufen
Rufen Sie dann die GetMethod auf, um Ihre generische Funktion zu finden:
Bisher ist der obige Aufruf ziemlich identisch mit dem oben erläuterten, jedoch mit einem kleinen Unterschied, wenn Sie mehrere Parameter an ihn übergeben müssen.
Sie müssen ein Array vom Typ [] an die Funktion MakeGenericMethod übergeben, die die oben erstellten "Dummy" -Objekttypen enthält:
Sobald dies erledigt ist, müssen Sie die Invoke-Methode wie oben erwähnt aufrufen.
Und du bist fertig. Wirkt ein Zauber!
AKTUALISIEREN:
Wie @Bevan hervorgehoben hat, muss ich beim Aufrufen der MakeGenericMethod-Funktion kein Array erstellen, da sie Parameter enthält, und ich muss kein Objekt erstellen, um die Typen abzurufen, da ich die Typen einfach direkt an diese Funktion übergeben kann. In meinem Fall habe ich meinen Code einfach geändert, da ich die Typen in einer anderen Klasse vordefiniert habe:
myClassInfo enthält 2 Eigenschaften des Typs,
Type
die ich zur Laufzeit basierend auf einem an den Konstruktor übergebenen Aufzählungswert festgelegt habe, und stellt mir die relevanten Typen zur Verfügung, die ich dann in der MakeGenericMethod verwende.Nochmals vielen Dank für das Hervorheben dieses @Bevan.
quelle
MakeGenericMethod()
das Schlüsselwort params, damit Sie kein Array erstellen müssen. Sie müssen auch keine Instanzen erstellen, um die Typen zu erhalten - diesmethodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject))
würde ausreichen.Inspiriert von der Antwort von Enigmativity - nehmen wir an, Sie haben zwei (oder mehr) Klassen wie
und Sie möchten die Methode
Foo<T>
mitBar
und aufrufenSquare
, die als deklariert istDann können Sie eine Erweiterungsmethode implementieren wie:
Mit diesem können Sie einfach
Foo
wie folgt aufrufen :das funktioniert für jede Klasse. In diesem Fall wird Folgendes ausgegeben:
quelle