¿Cómo uso la reflexión para llamar a un método genérico?
Frecuentes
Visto 278,262 equipos
1125
¿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 compilación, sino que se obtiene dinámicamente en tiempo de ejecución?
Considere el siguiente código de muestra, dentro del Example()
método, cuál es la forma más concisa de invocar GenericMethod<T>()
usando el Type
almacenado en el myType
¿variable?
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>()
{
//...
}
}
8 Respuestas
1191
Debe usar la reflexión para que el método comience, luego "construirlo" proporcionando argumentos de tipo con MakeGenericMethod:
MethodInfo method = typeof(Sample).GetMethod(nameof(Sample.GenericMethod));
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
Para un método estático, pase null
como el primer argumento para Invoke
. Eso no tiene nada que ver con los métodos genéricos, es solo una reflexión normal.
Como se señaló, mucho de esto es más simple a partir de C # 4 usando dynamic
- si puede usar la inferencia de tipos, por supuesto. No ayuda en los casos en los que la inferencia de tipos no está disponible, como el ejemplo exacto de la pregunta.
Respondido 29 Abr '20, 09:04
+1; tenga en cuenta que GetMethod()
solo considera los métodos de instancia pública de forma predeterminada, por lo que es posible que necesite BindingFlags.Static
y/o BindingFlags.NonPublic
. - usuario565869
La combinación correcta de banderas es BindingFlags.NonPublic | BindingFlags.Instance
(y opcionalmente BindingFlags.Static
). - Lars Kemmann
Una pregunta que se marca como inocente de esto se pregunta cómo hacer esto con métodos estáticos, y técnicamente también lo hace la pregunta aquí. El primer parámetro de generic.Invoke () debe ser nulo al llamar a métodos estáticos. El primer parámetro solo es necesario al llamar a métodos de instancia. - chris moschini
@ChrisMoschini: Agregó eso a la respuesta. - jon skeet
@gzou: agregué algo a la respuesta, pero tenga en cuenta que para llamar a los métodos genéricos en la pregunta, dynamic
no ayuda porque la inferencia de tipos no está disponible. (No hay argumentos que el compilador pueda usar para determinar el tipo de argumento). jon skeet
177
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 porque pierde la verificación en tiempo de compilación para GenericMethod
. Si luego hace una refactorización y cambia el nombre GenericMethod
, este código no se dará cuenta y fallará en tiempo de ejecución. Además, si hay algún procesamiento posterior del ensamblado (por ejemplo, ofuscar o eliminar métodos / clases no utilizados), este código también podría romperse.
Entonces, si conoce el método al que se está vinculando en el momento de la compilació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<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);
Si bien no es muy bonito, tiene una referencia de tiempo de compilación para GenericMethod
aquí, y si refactoriza, elimine o haga algo con GenericMethod
, este código seguirá funcionando, o al menos se interrumpirá en tiempo de compilación (si, por ejemplo, elimina GenericMethod
).
Otra forma de hacer lo mismo sería crear una nueva clase contenedora y crearla a través de Activator
. No sé si hay una mejor manera.
Respondido el 02 de junio de 16 a las 16:06
En los casos en los que se utiliza la reflexión para llamar a un método, es habitual que otro método descubra el nombre del método. No es común conocer el nombre del método de antemano. - Bevan
Bueno, estoy de acuerdo con los usos comunes de la reflexión. Pero la pregunta original era cómo llamar "Método genérico () "Si se permitiera esa sintaxis, no necesitaríamos GetMethod () en absoluto. Pero para la pregunta" cómo escribo "GenericMethod "? Creo que la respuesta debería incluir una forma de evitar perder el enlace en tiempo de compilación con GenericMethod. Ahora bien, si esta pregunta es común o no, no lo sé, pero sé que tuve este problema exacto ayer, y es por eso que aterrizó en esta pregunta. Adrián Gallero
Podrías hacerlo GenMethod.Method.GetGenericMethodDefinition()
en lugar de this.GetType().GetMethod(GenMethod.Method.Name)
. Es un poco más limpio y probablemente más seguro. - daniel cassidy
¿Qué significa "myType" en su muestra? - dmitryboyko
Ahora puedes usar nameof(GenericMethod)
- dmigo
150
Llamar a un método genérico con un parámetro de tipo conocido solo en tiempo de ejecución se puede simplificar enormemente mediante el uso de un dynamic
type en lugar de la API de reflexión.
Para utilizar esta técnica, el tipo debe conocerse a partir del objeto real (no solo una instancia del Type
clase). De lo contrario, debe crear un objeto de ese tipo o usar la API de reflexión estándar solución. Puede crear un objeto utilizando el Activador.CreateInstancia método.
Si desea llamar a un método genérico, que en el uso "normal" se habría inferido su tipo, entonces simplemente se trata de convertir el objeto de tipo desconocido a dynamic
. He aquí un ejemplo:
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)"
}
}
}
Y aquí está el resultado de este programa:
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 GetType()
método) y el tipo de parámetro genérico (utilizando typeof
operador).
Al lanzar el argumento del objeto a dynamic
type aplazamos el suministro del parámetro de tipo hasta el tiempo de ejecución. Cuando el Process
se llama al método con el dynamic
argumento, entonces al compilador no le importa el tipo de este argumento. El compilador genera código que en tiempo de ejecución verifica los tipos reales de argumentos pasados (usando la reflexión) y elige el mejor método para llamar. Aquí solo existe este método genérico, por lo que se invoca con un parámetro de tipo adecuado.
En este ejemplo, la salida es la misma 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 tipo dinámico es definitivamente más corta y más fácil de escribir. Tampoco debería preocuparse por el rendimiento de llamar a esta función varias veces. La siguiente llamada con argumentos del mismo tipo debería ser más rápida gracias al el almacenamiento en caché mecanismo en DLR. Por supuesto, puede escribir código que invoque a los delegados en caché, pero usando el dynamic
escriba, obtiene este comportamiento de forma gratuita.
Si el método genérico que desea llamar no tiene un argumento de un tipo parametrizado (por lo que su parámetro de tipo no se puede inferir), puede envolver la invocación del método genérico en un método auxiliar como en el siguiente ejemplo:
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) + ">");
}
}
Mayor seguridad de tipos
¿Qué tiene de bueno usar dynamic
como reemplazo del uso de la API de reflexión es que solo pierde la verificación del tiempo de compilación de este tipo en particular que no conoce hasta el tiempo de ejecución. El compilador analiza de forma estática otros argumentos y el nombre del método como de costumbre. Si elimina o agrega más argumentos, cambia sus tipos o cambia el nombre del método, obtendrá un error en tiempo de compilació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 detectar algunos errores en tiempo de compilació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>(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í de nuevo ejecutamos algún método lanzando el argumento a la dynamic
tipo. Solo la verificación del tipo del primer argumento se pospone al tiempo de ejecución. Obtendrá un error del compilador si el nombre del método al que está llamando no existe o si otros argumentos no son válidos (número incorrecto de argumentos o tipos incorrectos).
Cuando pasas el dynamic
argumento a un método, entonces esta llamada es últimamente atado. La resolución de sobrecarga del método ocurre en tiempo de ejecución e intenta elegir la mejor sobrecarga. Entonces, si invoca el ProcessItem
método con un objeto de BarItem
type, en realidad llamará al método no genérico, porque es una mejor coincidencia para este tipo. Sin embargo, obtendrá un error de tiempo de ejecución cuando pase un argumento del Alpha
escriba porque no hay un método que pueda manejar este objeto (un método genérico tiene la restricción where T : IItem
y Alpha
class no implementa esta interfaz). Pero ese es el punto. El compilador no tiene información de que esta llamada sea válida. Usted, como programador, lo sabe y debe asegurarse de que este código se ejecute sin errores.
Tipo de retorno gotcha
Cuando llama a un método no vacío con un parámetro de tipo dinámico, su tipo de retorno probablemente será be dynamic
muy. Entonces, si cambia el ejemplo anterior a este código:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
entonces el tipo de objeto de resultado sería dynamic
. Esto se debe a que el compilador no siempre sabe qué método se llamará. Si conoce el tipo de retorno de la llamada a la función, debería convertir implícitamente al tipo requerido para que el resto del código se escriba estáticamente:
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 de resultado en el ejemplo anterior, obtendrá un error de tiempo de ejecución en la segunda iteración del ciclo. Esto se debe a que intentó guardar el valor de retorno de una función nula.
Respondido 19 Oct 20, 23:10
Mariusz, confundido por "Sin embargo, obtendrá un error de tiempo de ejecución cuando pase un argumento de tipo Alpha porque no hay un método que pueda manejar este objeto". Si llamo a var a = new Alpha () ProcessItem (a, "test" + i , i) ¿Por qué el método ProcessItem genérico no manejaría esto de manera efectiva, generando "Elemento de proceso general"? - Alex Edelstein
@AlexEdelstein Edité mi respuesta para aclarar un poco. Es porque genérico ProcessItem
El método tiene una restricción genérica y acepta solo objetos que implementan IItem
interfaz. Cuando vas a llamar ProcessItem(new Aplha(), "test" , 1);
or ProcessItem((object)(new Aplha()), "test" , 1);
obtendrá un error del compilador, pero al enviar a dynamic
pospone ese cheque al tiempo de ejecución. - Mariusz Pawelski
Gran respuesta y explicación, funciona perfectamente para mí. Mucho mejor que la respuesta aceptada, más corto de escribir, más eficaz y más seguro. - ygoe
17
Con C # 4.0, la reflexión no es necesaria ya que el DLR puede llamarlo usando tipos de tiempo de ejecución. Dado que usar la biblioteca DLR es una especie de molestia dinámicamente (en lugar de que el compilador de C # genere código para usted), el marco de código abierto Dinamita (.net estándar 1.5) le brinda fácil acceso en tiempo de ejecución en caché a las mismas llamadas que el compilador generaría para usted.
var name = InvokeMemberName.Create;
Dynamic.InvokeMemberAction(this, name("GenericMethod", new[]{myType}));
var staticContext = InvokeContext.CreateStatic;
Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType}));
Respondido el 05 de enero de 18 a las 22:01
15
Agregando a La respuesta de Adrian Gallero:
Llamar a un método genérico desde el tipo info implica tres pasos.
TLDR: la llamada a un método genérico conocido con un objeto de tipo se puede lograr mediante:
((Action)GenericMethod<object>)
.Method
.GetGenericMethodDefinition()
.MakeGenericMethod(typeof(string))
.Invoke(this, null);
donde GenericMethod<object>
es el nombre del método a llamar y cualquier tipo que satisfaga las restricciones genéricas.
(Acción) coincide con la firma del método que se llamará, es decir (Func<string,string,int>
or Action<bool>
)
El paso 1 es obtener MethodInfo para la definición del método genérico
Método 1: use GetMethod () o GetMethods () con los 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 dentro de la clase que contiene los métodos:
MethodInfo method = ((Action)GenericMethod<object>)
.Method
.GetGenericMethodDefinition();
MethodInfo method = ((Action)StaticMethod<object>)
.Method
.GetGenericMethodDefinition();
Desde fuera de la clase que contiene los métodos:
MethodInfo method = ((Action)(new Sample())
.GenericMethod<object>)
.Method
.GetGenericMethodDefinition();
MethodInfo method = ((Action)Sample.StaticMethod<object>)
.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<object>)
se refiere al delegado para un método específico. ((Func<string, int>)GenericMethod<object>)
se 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<Action<Sample>>)(
(Sample v) => v.GenericMethod<object>()
)).Body).Method.GetGenericMethodDefinition();
Esto se descompone en
Cree una expresión lambda donde el cuerpo sea una llamada a su método deseado.
Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();
Extraiga el cuerpo y conviértalo en 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 es llamar a MakeGenericMethod para crear un método genérico con los tipos apropiados.
MethodInfo generic = method.MakeGenericMethod(myType);
El paso 3 es invocar el método con los argumentos apropiados.
generic.Invoke(this, null);
contestado el 23 de mayo de 17 a las 12:05
9
Nadie proporcionó el "reflexión clásica", así que aquí hay un ejemplo de código completo:
using System;
using System.Collections;
using System.Collections.Generic;
namespace DictionaryRuntime
{
public class DynamicDictionaryFactory
{
/// <summary>
/// Factory to create dynamically a generic Dictionary.
/// </summary>
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<K,T>.
IDictionary d = Activator.CreateInstance(genericType) as IDictionary;
return d;
}
}
}
Lo anterior DynamicDictionaryFactory
la clase tiene un método
CreateDynamicGenericInstance(Type keyType, Type valueType)
y crea y devuelve una instancia de IDictionary, cuyos tipos de claves y valores son exactamente los especificados en la llamada keyType
y valueType
.
Aquí hay un ejemplo completo. cómo llamar a este método para crear una instancia y usar un Dictionary<String, int>
:
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<String, int>;
if (typedDict != null)
{
Console.WriteLine("Dictionary<String, int>");
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 consola anterior, obtenemos el resultado esperado correcto:
Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3
Respondido 24 ago 16, 02:08
2
Estos son mis 2 centavos basados en La respuesta de Grax, pero con dos parámetros necesarios para un método genérico.
Suponga que su método se define de la siguiente manera en una clase Helpers:
public class Helpers
{
public static U ConvertCsvDataToCollection<U, T>(string csvData)
where U : ObservableCollection<T>
{
//transform code here
}
}
En mi caso, el tipo U es siempre una colección observable que almacena un objeto 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 a Make
object myCollection = Activator.CreateInstance(collectionType);
object myoObject = Activator.CreateInstance(objectType);
Luego llame al 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 la que se explicó anteriormente, pero con una pequeña diferencia cuando es necesario pasarle múltiples parámetros.
Debe pasar una matriz Type [] 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 de maravilla!
ACTUALIZACIÓN:
Como destacó @Bevan, no necesito crear una matriz cuando llamo a la función MakeGenericMethod, ya que toma parámetros 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 de tipo Type
que configuro en tiempo de ejecución en función de un valor de enumeración pasado al constructor y me proporcionará los tipos relevantes que luego uso en MakeGenericMethod.
Gracias nuevamente por resaltar esto @Bevan.
contestado el 23 de mayo de 17 a las 13:05
Los argumentos para MakeGenericMethod()
tienen el params palabra clave para que no necesite crear una matriz; ni es necesario crear instancias para obtener los tipos, methodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject))
sería suficiente. - Bevan
0
Inspirado por La respuesta de la enigmatividad - supongamos que tienes dos (o más) clases, como
public class Bar { }
public class Square { }
y quieres llamar al método Foo<T>
a Bar
y Square
, que se declara como
public class myClass
{
public void Foo<T>(T item)
{
Console.WriteLine(typeof(T).Name);
}
}
Entonces puedes implementar un Método de extensión me gusta:
public static class Extension
{
public static void InvokeFoo<T>(this T t)
{
var fooMethod = typeof(myClass).GetMethod("Foo");
var tType = typeof(T);
var fooTMethod = fooMethod.MakeGenericMethod(new[] { tType });
fooTMethod.Invoke(new myClass(), new object[] { t });
}
}
Con esto, simplemente puede invocar Foo
me gusta:
var objSquare = new Square();
objSquare.InvokeFoo();
var objBar = new Bar();
objBar.InvokeFoo();
que funciona para todas las clases. En este caso, generará:
Square
Bar
respondido 03 mar '20, 10:03
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas c# .net generics reflection or haz tu propia pregunta.
Probé la solución de Jon y no pude hacer que funcionara hasta que hice público el método genérico en mi clase. Sé que otro Jon respondió diciendo que necesita especificar las banderas de enlace, pero esto no ayudó. - naskew
También necesitas
BindingFlags.Instance
, No sóloBindingFlags.NonPublic
, para obtener el método privado / interno. - Lars KemmannVersión moderna de esta pregunta: stackoverflow.com/q/2433436/103167 - Ben Voigt
@Peter Mortensen - para su información, usé espacios antes del '?' para separar las partes en inglés de las partes que no están en inglés (C #); En mi humilde opinión, quitar el espacio hace que se vea como el? es parte del código. Si no hubiera código, ciertamente estaría de acuerdo con eliminar los espacios, pero en este caso ... - Bevan
Podemos definir un método genérico y luego usar el método GetMethod para obtener toda la información del método genérico y usarlo. - elnaz jangi