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.
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 nurBindingFlags.NonPublic
, um die private/interne Methode zu erhalten.2 Stimmen
Die moderne Version dieser Frage: stackoverflow.com/q/2433436/103167
0 Stimmen
@Peter Mortensen - zu Ihrer Information: Ich habe Leerzeichen vor dem '?' verwendet, um die englischen Teile von den nicht-englischen (C#) Teilen zu trennen; IMHO lässt das Entfernen des Leerzeichens es so aussehen, als ob das ? Teil des Codes ist. Wenn es keinen Code gäbe, würde ich sicherlich zustimmen, die Leerzeichen zu entfernen, aber in diesem Fall ...
0 Stimmen
Wir können eine generische Methode definieren und dann die GetMethod-Methode verwenden, um alle Informationen über die generische Methode zu erhalten und sie zu verwenden.