Pregunta ¿Cómo uso el reflejo 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 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>() utilizando 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>()
    {
        //...
    }
}

844
2017-10-24 05:17


origen


Respuestas:


Necesita usar la reflexión para comenzar con el método, luego "construirlo" proporcionando argumentos de tipo con 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 el primer argumento para Invoke. Eso no tiene nada que ver con los métodos genéricos; es solo un reflejo normal.

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


921
2017-10-24 06:13



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 compilación 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 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 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);

Aunque no es muy bonito, tiene una referencia de tiempo de compilación GenericMethod aquí, y si refactoriza, elimina o hace algo con GenericMethod, este código seguirá funcionando, o al menos se romperá en el momento de la compilación (si, por ejemplo, eliminas 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.


135
2018-02-27 16:11



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 dynamic escriba en lugar de la API de reflexión.

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

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

Al convertir el argumento del objeto en dynamic tipo difirimos proporcionando el parámetro de tipo hasta el tiempo de ejecución. Cuando el Process método se llama con el dynamic argumento entonces el compilador no se preocupa por el tipo de este argumento. El compilador 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 dinámico 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 a la almacenamiento en caché mecanismo en DLR. Por supuesto, puede escribir el código que el caché invocó a los delegados, pero al usar el dynamic tipo obtienes este comportamiento gratis.

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>(T obj)
    {
        GenericMethod<T>();
    }

    public static void GenericMethod<T>()
    {
        Console.WriteLine("GenericMethod<" + typeof(T) + ">");
    }
}

Mayor seguridad de tipo

Lo que es realmente genial sobre usar dynamic El objeto como reemplazo para usar la API de reflexión es que solo pierde la verificación del tiempo de compilación de este tipo particular que no conoce hasta el tiempo de ejecución. Otros argumentos y el nombre del método son analizados estáticamente por el compilador 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 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 capturar 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í ejecutamos de nuevo algún método lanzando el argumento al dynamic tipo. Solo la verificación del tipo del primer argumento se pospone al tiempo de ejecución. Obtendrá un error de compilació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 pasas el dynamic argumento a un método, entonces esta llamada es últimamente atado. La resolución de sobrecarga del método ocurre en el tiempo de ejecución e intenta elegir la mejor sobrecarga. Entonces si invocas el ProcessItem método con un objeto de BarItem escriba entonces realmente 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 ningún método que pueda manejar este objeto (un método genérico tiene la restricción where T : IItem y Alpha la clase no implementa esta interfaz). Pero ese es todo el punto. El compilador no tiene información de que esta llamada es válida. Usted como programador 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 dinámico, es probable que su tipo de devolución sea ser dynamic también. 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 compilador no siempre sabe a qué método se llamará. Si conoce el tipo de devolución de la llamada de función, entonces debería convertir implícitamente al tipo requerido para que el resto 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.


105
2018-03-16 19:21



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 el uso de la biblioteca DLR es una especie de dolor dinámico (en lugar de que el compilador C # genere código para usted), el marco de código abierto Dynamitey (.NET estándar 1.5) le proporciona un 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}));

11
2017-07-05 13:40



Agregando a La respuesta de Adrián 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<object>)
    .Method
    .GetGenericMethodDefinition()
    .MakeGenericMethod(typeof(string))
    .Invoke(this, null);

dónde GenericMethod<object> 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á, es decir (Func<string,string,int> o Action<bool>)

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<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 proporcione los tipos de los parámetros del método, no se sabe qué método al que 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

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

Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();

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

5
2018-01-09 22:20



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

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 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 instancias y utilizar 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 la consola anterior, obtenemos el resultado correcto y esperado:

Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3

2
2017-08-24 01:24



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<U, T>(string csvData)
    where U : ObservableCollection<T>
    {
      //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 de tipo Type que configuré 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.


0
2017-10-22 23:28