¿Cómo uso el reflection para llamar a un método genérico?

¿Cuál es la mejor manera de llamar a un método genérico cuando el parámetro de tipo no se conoce en tiempo de comstackción, sino que se obtiene dinámicamente en tiempo de ejecución?

Considere el siguiente código de ejemplo: dentro del método Example() , ¿cuál es la forma más concisa de invocar GenericMethod() usando el Type almacenado en la variable myType ?

 public class Sample { public void Example(string typeName) { Type myType = FindType(typeName); // What goes here to call GenericMethod()? GenericMethod(); // This doesn't work // What changes to call StaticMethod()? Sample.StaticMethod(); // This also doesn't work } public void GenericMethod() { // ... } public static void StaticMethod() { //... } } 

Necesita usar la reflexión para comenzar con el método, luego “construirlo” al proporcionar argumentos de tipo con el método MakeGenericMethod :

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

Para un método estático, pase null como primer argumento para Invoke . Eso no tiene nada que ver con los métodos generics; es solo un reflection normal.

Como se señaló, mucho de esto es más simple a partir de C # 4 usando dynamic , si se puede usar la inferencia de tipo, por supuesto. No ayuda en casos donde la inferencia de tipo no está disponible, como el ejemplo exacto en la pregunta.

Solo una adición a la respuesta original. Si bien esto funcionará:

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

También es un poco peligroso que pierda el control en tiempo de GenericMethod para GenericMethod . Si luego realiza una refactorización y renombra GenericMethod , este código no se dará cuenta y fallará en tiempo de ejecución. Además, si hay algún procesamiento posterior del ensamblaje (por ejemplo, ofuscación o eliminación de métodos / clases no utilizados), este código también podría romperse.

Entonces, si conoce el método al que está vinculando en tiempo de comstackción, y esto no se llama millones de veces, por lo que la sobrecarga no importa, cambiaría este código para que sea:

 Action<> GenMethod = GenericMethod; //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); 

Aunque no es muy bonito, aquí tiene una referencia de tiempo de comstackción a GenericMethod , y si refactoriza, elimina o hace algo con GenericMethod , este código seguirá funcionando, o al menos interrumpirá en tiempo de comstackción (si, por ejemplo, elimina GenericMethod ).

Otra forma de hacer lo mismo sería crear una nueva clase contenedora y crearla a través del Activator . No sé si hay una mejor manera.

Llamar a un método genérico con un parámetro de tipo conocido solo en tiempo de ejecución se puede simplificar enormemente utilizando un tipo dynamic lugar de la API de reflexión.

Para utilizar esta técnica, el tipo debe conocerse desde el objeto real (no solo una instancia de la clase Type ). De lo contrario, debe crear un objeto de ese tipo o utilizar la solución API de reflexión estándar. Puede crear un objeto usando el método Activator.CreateInstance .

Si desea llamar a un método genérico, que en el uso “normal” habría inferido su tipo, entonces simplemente se trata de convertir el objeto de tipo desconocido en dynamic . Aquí hay un ejemplo:

 class Alpha { } class Beta { } class Service { public void Process(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(a)" service.Process(b); // Same as "service.Process(b)" var objects = new object[] { a, b }; foreach (var o in objects) { service.Process(o); // Same as "service.Process(o)" } foreach (var o in objects) { dynamic dynObj = o; service.Process(dynObj); // Or write "service.Process((dynamic)o)" } } } 

Y aquí está el resultado de este progtwig:

 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 es un método de instancia genérico que escribe el tipo real del argumento pasado (utilizando el método GetType() ) y el tipo del parámetro genérico (utilizando el operador typeof ).

Al convertir el argumento del objeto al tipo dynamic , diferimos proporcionando el parámetro de tipo hasta el tiempo de ejecución. Cuando se llama al método Process con el argumento dynamic , al comstackdor no le importa el tipo de este argumento. El comstackdor genera código que en tiempo de ejecución verifica los tipos reales de argumentos pasados ​​(mediante el uso de la reflexión) y elige el mejor método para llamar. Aquí solo hay este único método genérico, por lo que se invoca con un parámetro de tipo apropiado.

En este ejemplo, el resultado es el mismo que si escribiera:

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

La versión con un tipo dynamic es definitivamente más corta y más fácil de escribir. Tampoco debe preocuparse por el rendimiento de llamar a esta función varias veces. La próxima llamada con argumentos del mismo tipo debería ser más rápida gracias al mecanismo de caché en DLR. Por supuesto, puede escribir el código que el caché invocó a los delegates, pero al usar el tipo dynamic obtiene este comportamiento de forma gratuita.

Si el método genérico que desea llamar no tiene un argumento de tipo parametrizado (por lo que su parámetro de tipo no se puede inferir), puede ajustar la invocación del método genérico en un método de ayuda como en el siguiente ejemplo:

 class Program { static void Main(string[] args) { object obj = new Alpha(); Helper((dynamic)obj); } public static void Helper(T obj) { GenericMethod(); } public static void GenericMethod() { Console.WriteLine("GenericMethod<" + typeof(T) + ">"); } } 

Mayor seguridad de tipo

Lo que es realmente genial sobre el uso de objetos dynamic como un reemplazo para usar la API de reflexión es que solo pierdes la verificación de tiempo de comstackción de este tipo particular que no conoces hasta el tiempo de ejecución. Otros argumentos y el nombre del método son analizados estáticamente por el comstackdor como de costumbre. Si elimina o agrega más argumentos, cambia sus tipos o renombra el nombre del método, obtendrá un error en tiempo de comstackción. Esto no sucederá si proporciona el nombre del método como una cadena en Type.GetMethod y argumentos como la matriz de objetos en MethodInfo.Invoke .

A continuación se muestra un ejemplo simple que ilustra cómo se pueden capturar algunos errores en tiempo de comstackción (código comentado) y otros en tiempo de ejecución. También muestra cómo el DLR intenta resolver qué método llamar.

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

Aquí nuevamente ejecutamos algún método al convertir el argumento al tipo dynamic . Solo la verificación del tipo del primer argumento se pospone al tiempo de ejecución. Obtendrá un error de comstackción si el nombre del método al que llama no existe o si otros argumentos son inválidos (número incorrecto de argumentos o tipos incorrectos).

Cuando pasa el argumento dynamic a un método, esta llamada se enlaza últimamente . La resolución de sobrecarga del método ocurre en el tiempo de ejecución e intenta elegir la mejor sobrecarga. Por lo tanto, si invoca el método ProcessItem con un objeto de tipo BarItem , en realidad llamará al método no genérico, porque es la mejor opción para este tipo. Sin embargo, obtendrá un error de tiempo de ejecución cuando pase un argumento del tipo Alpha porque no hay ningún método que pueda manejar este objeto (un método genérico tiene la restricción where T : IItem y Alpha clase Alpha no implementan esta interfaz). Pero ese es todo el punto. El comstackdor no tiene información de que esta llamada es válida. Usted como progtwigdor lo sabe y debe asegurarse de que este código se ejecute sin errores.

Tipo de devolución gotcha

Cuando llama a un método no nulo con un parámetro de tipo dynamic, su tipo de devolución también será dynamic . Entonces, si cambias el ejemplo anterior a este código:

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

entonces el tipo del objeto resultante sería dynamic . Esto se debe a que el comstackdor no siempre sabe a qué método se llamará. Si conoce el tipo de devolución de la llamada a la función, debe convertirla implícitamente al tipo requerido para que el rest del código esté escrito de forma estática:

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

Obtendrá un error de tiempo de ejecución si el tipo no coincide.

En realidad, si intenta obtener el valor del resultado en el ejemplo anterior, obtendrá un error de tiempo de ejecución en la segunda iteración del bucle. Esto se debe a que trataste de guardar el valor de retorno de una función vacía.

Con C # 4.0, la reflexión no es necesaria ya que el DLR puede llamarla utilizando tipos de tiempo de ejecución. Dado que utilizar la biblioteca DLR es una especie de dolor dynamic (en lugar del código de generación del comstackdor C #), el marco de código abierto Dynamitey (.NET estándar 1.5) le proporciona un fácil acceso en tiempo de ejecución a las mismas llamadas que el comstackdor generaría para ti.

 var name = InvokeMemberName.Create; Dynamic.InvokeMemberAction(this, name("GenericMethod", new[]{myType})); var staticContext = InvokeContext.CreateStatic; Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType})); 

Agregando a la respuesta de Adrian Gallero :

Llamar a un método genérico desde la información de tipo implica tres pasos.

TLDR: Llamar a un método genérico conocido con un objeto tipo puede lograrse mediante:

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

donde GenericMethod es el nombre del método para llamar y cualquier tipo que satisfaga las restricciones genéricas.

(Acción) coincide con la firma del método que se llamará ( Func o Action )

El paso 1 es obtener MethodInfo para la definición del método genérico

Método 1: utilice GetMethod () o GetMethods () con tipos apropiados o indicadores de enlace.

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

Método 2: cree un delegado, obtenga el objeto MethodInfo y luego llame a GetGenericMethodDefinition

Desde el interior de la clase que contiene los métodos:

 MethodInfo method = ((Action)GenericMethod) .Method .GetGenericMethodDefinition(); MethodInfo method = ((Action)StaticMethod) .Method .GetGenericMethodDefinition(); 

Desde fuera de la clase que contiene los métodos:

 MethodInfo method = ((Action)(new Sample()) .GenericMethod) .Method .GetGenericMethodDefinition(); MethodInfo method = ((Action)Sample.StaticMethod) .Method .GetGenericMethodDefinition(); 

En C #, el nombre de un método, es decir, “ToString” o “GenericMethod” en realidad se refiere a un grupo de métodos que pueden contener uno o más métodos. Hasta que no proporcione los tipos de parámetros del método, no se sabe a qué método se refiere.

((Action)GenericMethod) refiere al delegado para un método específico. ((Func)GenericMethod) refiere a una sobrecarga diferente de GenericMethod

Método 3: cree una expresión lambda que contenga una expresión de llamada de método, obtenga el objeto MethodInfo y luego GetGenericMethodDefinition

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

Esto se descompone en

Crea una expresión lambda donde el cuerpo sea una llamada al método que desees.

 Expression> expr = (Sample v) => v.GenericMethod(); 

Extraiga el cuerpo y moldee a MethodCallExpression

 MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body; 

Obtenga la definición del método genérico del método

 MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition(); 

El paso 2 está llamando a MakeGenericMethod para crear un método genérico con los tipos apropiados.

 MethodInfo generic = method.MakeGenericMethod(myType); 

El paso 3 invoca el método con los argumentos apropiados.

 generic.Invoke(this, null); 

Nadie proporcionó la solución ” Reflexión clásica “, así que aquí hay un ejemplo completo de código:

 using System; using System.Collections; using System.Collections.Generic; namespace DictionaryRuntime { public class DynamicDictionaryFactory { ///  /// Factory to create dynamically a generic Dictionary. ///  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. IDictionary d = Activator.CreateInstance(genericType) as IDictionary; return d; } } } 

La clase DynamicDictionaryFactory anterior tiene un método

CreateDynamicGenericInstance(Type keyType, Type valueType)

y crea y devuelve una instancia IDictionary, cuyos tipos de claves y valores son exactamente los especificados en la llamada keyType y valueType .

Aquí hay un ejemplo completo de cómo llamar a este método para crear instancias y usar un Dictionary :

 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; if (typedDict != null) { Console.WriteLine("Dictionary"); 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"); } } } 

Cuando se ejecuta la aplicación de la consola anterior, obtenemos el resultado correcto y esperado:

 Dictionary "One": 1 "Two": 2 "Three": 3 

Este es mi 2 centavos basado en la respuesta de Grax , pero con dos parámetros requeridos para un método genérico.

Suponga que su método se define de la siguiente manera en una clase de Ayudantes:

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

En mi caso, el tipo U es siempre un objeto de almacenamiento de colección observable de tipo T.

Como tengo mis tipos predefinidos, primero creo los objetos “ficticios” que representan la colección observable (U) y el objeto almacenado en ella (T) y que se usarán a continuación para obtener su tipo al llamar al Make

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

Luego llame a GetMethod para encontrar su función genérica:

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

Hasta ahora, la llamada anterior es prácticamente idéntica a lo que se explicó anteriormente, pero con una pequeña diferencia cuando se necesita pasarle múltiples parámetros.

Necesita pasar una matriz Tipo [] a la función MakeGenericMethod que contiene los tipos de “objetos ficticios” que se crearon anteriormente:

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

Una vez hecho esto, debe llamar al método Invoke como se mencionó anteriormente.

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

Y tu estas listo. Funciona un encanto!

ACTUALIZAR:

Como @Bevan destacó, no necesito crear una matriz cuando llamo a la función MakeGenericMethod ya que toma params y no necesito crear un objeto para obtener los tipos, ya que puedo pasar los tipos directamente a esta función. En mi caso, dado que tengo los tipos predefinidos en otra clase, simplemente cambié mi código a:

 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 contiene 2 propiedades del tipo Type que establezco en tiempo de ejecución en función de un valor enum pasado al constructor y me proporcionará los tipos relevantes que luego usaré en el método MakeGenericMethod.

Gracias de nuevo por destacar este @Bevan.