c# como - ¿Cómo uso la reflexión para llamar a un método genérico?




studio ponen (7)

¿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 método Example() , ¿cuál es la forma más concisa de invocar GenericMethod<T>() 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<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>()
    {
        //...
    }
}

Answers

Con C # 4.0, la reflexión no es necesaria ya que el DLR puede llamarlo usando tipos de tiempo de ejecución. Como el uso de la biblioteca DLR es un tipo de dolor dinámico (en lugar del código que genera el compilador de C # para usted), el framework de código abierto Dynamitey (.net standard 1.5) le brinda fácil acceso en tiempo de ejecución en caché a las mismas llamadas que generaría el compilador 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}));

Sólo 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 ya que pierde la verificación en tiempo de GenericMethod para GenericMethod . Si luego hace una refactorización y cambia el nombre de GenericMethod , este código no se dará cuenta y fallará en el tiempo de ejecución. Además, si hay algún procesamiento posterior del ensamblaje (por ejemplo, ofuscar o eliminar métodos / clases no utilizados), este código también podría fallar.

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 no importa la sobrecarga, 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 a GenericMethod aquí, y si refactoriza, elimina o hace algo con GenericMethod , este código seguirá funcionando, o al menos se interrumpirá en el momento de la 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.


La utilización de 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 usar esta técnica, el tipo debe ser conocido por el objeto real (no solo una instancia de la clase de Type ). De lo contrario, debe crear un objeto de ese tipo o usar la solution API de reflexión estándar. Puede crear un objeto utilizando el método Activator.CreateInstance .

Si desea llamar a un método genérico, en el uso "normal" se 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á la salida 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 (usando el método GetType() ) y el tipo del parámetro genérico (usando el operador typeof ).

Al convertir el argumento del objeto en dynamic tipo dynamic , diferimos proporcionando el parámetro type hasta el tiempo de ejecución. Cuando se llama al método Process con el argumento dynamic , al compilador no le importa el tipo de este argumento. El compilador genera un código que en tiempo de ejecución comprueba los tipos reales de argumentos pasados ​​(mediante la reflexión) y elige el mejor método para llamar. Aquí solo hay un 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 escribieras:

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 al mecanismo de caching en caching en DLR. Por supuesto, puede escribir código para que la memoria caché invoque a los delegados, pero al usar el tipo dynamic , obtiene este comportamiento de forma gratuita.

Si el método genérico al que desea llamar no tiene un argumento de tipo parametrizado (por lo que no se puede inferir su parámetro de tipo), puede ajustar 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 tipo

Lo que es realmente genial de usar dynamic objeto 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 algunos errores pueden detectarse en tiempo de compilación (código comentado) y otros en tiempo de ejecución. También muestra cómo el DLR intenta resolver a 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í nuevamente ejecutamos algún método al enviar el argumento al tipo dynamic . Solo la verificación del tipo del primer argumento se pospone al tiempo de ejecución. Recibirá un error de compilación si el nombre del método que está llamando no existe o si otros argumentos no son válidos (número de argumentos incorrecto o tipos incorrectos).

Cuando pasa el argumento dynamic a un método, esta llamada está vinculada últimamente . La resolución de sobrecarga del método ocurre en tiempo de ejecución y trata de elegir la mejor sobrecarga. Entonces, si invoca el método ProcessItem con un objeto de tipo BarItem , 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 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 clase where T : IItem y Alpha 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 retorno gotcha

Cuando llama a un método no vacío con un parámetro de tipo dinámico, su tipo de retorno probablemente también será dynamic . Así que si cambias el ejemplo anterior a este código:

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

entonces el tipo del objeto de resultado sería dynamic . Esto se debe a que el compilador no siempre sabe a qué método se llamará. Si conoce el tipo de retorno de la llamada a la función, entonces debe convertirlo implícitamente al tipo requerido para que el resto del código se escriba 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 intentó guardar el valor de retorno de una función nula.


Estos son mis 2 centavos basados ​​en la respuesta de Grax , pero con dos parámetros necesarios para un método genérico.

Supongamos 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 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 utilizará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 es necesario pasarle varios 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 Invocar como se mencionó anteriormente.

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

Y tu estas listo. Funciona un encanto!

ACTUALIZAR:

Como @Bevan resaltó, no necesito crear una matriz al llamar a la función MakeGenericMethod como se hace en parámetros y no necesito crear un objeto para obtener los tipos, ya que solo puedo pasar los tipos directamente a esta función. En mi caso, ya 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 establecí en el tiempo de ejecución en base a un valor enumero que se pasó al constructor y me proporcionará los tipos relevantes que luego uso en el método MakeGenericMethod.

Gracias de nuevo por resaltar este @Bevan.


Necesitas usar la reflexión para comenzar con el método, luego " MakeGenericMethod " 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 primer argumento a Invoke . Eso no tiene nada que ver con los métodos genéricos, es solo una reflexión normal.

Como se mencionó, mucho de esto es más simple desde C # 4 usando dynamic , si puede usar la inferencia de tipos, por supuesto. No ayuda en los casos en que la inferencia de tipos no está disponible, como el ejemplo exacto en la pregunta.


Añadiendo a la respuesta de Adrian Gallero :

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

TLDR: Llamar 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 al que se llama y cualquier tipo que satisfaga las restricciones genéricas.

(Acción) coincide con la firma del método que se va a llamar, es decir ( Func<string,string,int> o Action<bool> )

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

Método 1: Utilice GetMethod () o GetMethods () con los tipos adecuados o las banderas de enlace.

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

Método 2: crear un delegado, obtener el objeto MethodInfo y luego llamar 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>) refiere al delegado para un método específico. ((Func<string, int>)GenericMethod<object>) 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 a

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

Extraer el cuerpo y lanzarlo a MethodCallExpression.

MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;

Obtener 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 el tipo (s) apropiado (s).

MethodInfo generic = method.MakeGenericMethod(myType);

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

generic.Invoke(this, null);

Solo quería agregar que Bertrand Meyer, el inventor de Eiffel y el diseño por contrato, habrían Teamheredado de List<Player>tanto como de un pestillo.

En su libro, Construcción de software orientado a objetos , analiza la implementación de un sistema GUI donde las ventanas rectangulares pueden tener ventanas secundarias. Simplemente ha Windowheredado de ambos Rectangley Tree<Window>reutilizar la implementación.

Sin embargo, C # no es Eiffel. Este último admite herencia múltiple y cambio de nombre de características . En C #, cuando realiza una subclase, hereda tanto la interfaz como la implementación. Puede anular la implementación, pero las convenciones de llamada se copian directamente desde la superclase. En Eiffel, sin embargo, puede modificar los nombres de los métodos públicos, para que pueda cambiar el nombre Addy Removepara Hirey Fireen su Team. Si una instancia de Teames upcast de nuevo a List<Player>la persona que llama se utilice Addy Removepara modificarlo, pero sus métodos virtuales Hirey Fireserá llamado.







c# generics reflection