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:
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 (mithilfe der GetType()
Methode) und den Typ des generischen Parameters (mithilfe des typeof
Operators) schreibt .
Durch Umwandeln des Objektarguments in dynamic
Typ haben wir die Bereitstellung des Typparameters bis zur Laufzeit verschoben. Wenn die Process
Methode mit dem dynamic
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:
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 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:
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 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 Zeichenfolge Type.GetMethod
und Argumente als Objektarray angeben MethodInfo.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.
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 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 die ProcessItem
Methode mit einem Objekt vom BarItem
Typ aufrufen, rufen Sie die nicht generische Methode auf, da sie besser zu diesem Typ passt. Wenn Sie ein Argument des Alpha
Typs übergeben , wird jedoch ein Laufzeitfehler angezeigt, da es keine Methode gibt, die dieses Objekt verarbeiten kann (eine generische Methode hat die Einschränkung where T : IItem
und die Alpha
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:
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 ihn implizit in den erforderlichen Typ konvertieren , damit der Rest des Codes statisch typisiert wird:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
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.