¿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 la 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>()
{
//...
}
}
c#
.net
generics
reflection
Bevan
fuente
fuente
BindingFlags.Instance
, no soloBindingFlags.NonPublic
, obtener el método privado / interno.Respuestas:
Debe usar la reflexión para comenzar con el método, luego "construirlo" proporcionando argumentos de tipo con MakeGenericMethod :
Para un método estático, pase
null
como primer argumento aInvoke
. 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 del uso de C # 4
dynamic
, por supuesto, si puede usar la inferencia de tipos. No ayuda en casos donde la inferencia de tipos no está disponible, como el ejemplo exacto en la pregunta.fuente
GetMethod()
solo considera los métodos de instancia pública de forma predeterminada, por lo que puede necesitarBindingFlags.Static
y / oBindingFlags.NonPublic
.BindingFlags.NonPublic | BindingFlags.Instance
(y opcionalmenteBindingFlags.Static
).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.)Solo una adición a la respuesta original. Si bien esto funcionará:
También es un poco peligroso ya que pierde la verificación en tiempo de compilación
GenericMethod
. Si luego realiza una refactorización y cambia el nombreGenericMethod
, 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 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:
Si bien no es muy bonito, tiene una referencia de tiempo de compilación
GenericMethod
aquí, y si refactoriza, elimina o hace algoGenericMethod
, este código seguirá funcionando, o al menos se romperá en el momento de la compilación (si, por ejemplo, lo eliminaGenericMethod
).Otra forma de hacer lo mismo sería crear una nueva clase de contenedor y crearla
Activator
. No sé si hay una mejor manera.fuente
GenMethod.Method.GetGenericMethodDefinition()
lugar dethis.GetType().GetMethod(GenMethod.Method.Name)
. Es un poco más limpio y probablemente más seguro.nameof(GenericMethod)
Llamar a un método genérico con un parámetro de tipo conocido solo en tiempo de ejecución puede simplificarse enormemente usando un
dynamic
tipo en lugar de la API de reflexión.Para usar esta técnica, el tipo debe conocerse desde el objeto real (no solo una instancia de la
Type
clase). 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 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 enviar el objeto de tipo desconocido
dynamic
. Aquí hay un ejemplo:Y aquí está el resultado de este programa:
Process
es un método de instancia genérico que escribe el tipo real del argumento pasado (usando elGetType()
método) y el tipo del parámetro genérico (usando eltypeof
operador).Al convertir el argumento del objeto a
dynamic
tipo, diferimos proporcionando el parámetro de tipo hasta el tiempo de ejecución. CuandoProcess
se llama al método con eldynamic
argumento, al compilador no le importa el tipo de este argumento. El compilador genera código que en tiempo de ejecución comprueba los tipos reales de argumentos pasados (mediante el uso de 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, el resultado es el mismo que si escribiera:
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 almacenamiento en caché en DLR. Por supuesto, puede escribir código que cachee a los delegados invocados, pero al usar el
dynamic
tipo obtendrá 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:
Mayor seguridad de tipo
Lo realmente bueno de usar un
dynamic
objeto como reemplazo para usar la API de reflexión es que solo pierde el tiempo de compilación de este tipo en 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 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 cadenaType.GetMethod
y los argumentos como la matriz de objetosMethodInfo.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.
Aquí nuevamente ejecutamos 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 está llamando no existe o si otros argumentos no son válidos (número incorrecto de argumentos o tipos incorrectos).Cuando pasa el
dynamic
argumento a un método, esta llamada está vinculada últimamente . La resolución de sobrecarga del método ocurre en tiempo de ejecución e intenta elegir la mejor sobrecarga. Entonces, si invoca elProcessItem
método con un objeto deBarItem
tipo, 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 delAlpha
tipo porque no hay ningún método que pueda manejar este objeto (un método genérico tiene la restricciónwhere T : IItem
y laAlpha
clase no implementa esta interfaz). Pero ese es todo el punto. El compilador no tiene información de que esta llamada sea válida. Usted, como programador, sabe esto y debe asegurarse de que este código se ejecute sin errores.Tipo de retorno gotcha
Cuando se llama a un método no vacío con un parámetro de tipo dinámico, su tipo de retorno probablemente también lo sea
dynamic
. Entonces, si cambiara el ejemplo anterior a este código:entonces el tipo del objeto resultante 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, debe convertirlo implícitamente al tipo requerido para que el resto del código se escriba estáticamente: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 anulada.
fuente
ProcessItem
método genérico tiene una restricción genérica y acepta solo objetos que implementan laIItem
interfaz. Cuándo llamarásProcessItem(new Aplha(), "test" , 1);
oProcessItem((object)(new Aplha()), "test" , 1);
recibirás un error del compilador, pero al enviarlodynamic
pospones esa verificación en tiempo de ejecución.Con C # 4.0, la reflexión no es necesaria ya que el DLR puede llamarla usando tipos de tiempo de ejecución. Dado que el uso de la biblioteca DLR es una molestia dinámica (en lugar de que el compilador de C # genere código para usted), el marco de código abierto Dynamitey (.net estándar 1.5) le brinda un fácil acceso en tiempo de ejecución en caché a las mismas llamadas que generaría el compilador para ti.
fuente
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: la llamada a un método genérico conocido con un objeto tipo se puede lograr mediante:
donde
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 a llamar, es decir (
Func<string,string,int>
oAction<bool>
)El paso 1 es obtener el MethodInfo para la definición del método genérico
Método 1: Use GetMethod () o GetMethods () con los tipos apropiados o banderas de enlace.
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:
Desde fuera de la clase que contiene los métodos:
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 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 GenericMethodMétodo 3: Cree una expresión lambda que contenga una expresión de llamada a método, obtenga el objeto MethodInfo y luego GetGenericMethodDefinition
Esto se descompone en
Cree una expresión lambda donde el cuerpo sea una llamada a su método deseado.
Extraer el cuerpo y emitir a MethodCallExpression
Obtenga la definición del método genérico del método
El paso 2 está llamando a MakeGenericMethod para crear un método genérico con los tipos apropiados.
El paso 3 invoca el método con los argumentos apropiados.
fuente
Nadie proporcionó la solución de " reflexión clásica ", así que aquí hay un ejemplo de código completo:
La
DynamicDictionaryFactory
clase anterior tiene un métodoCreateDynamicGenericInstance(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
yvalueType
.Aquí hay un ejemplo completo de cómo llamar a este método para crear instancias y usar un
Dictionary<String, int>
:Cuando se ejecuta la aplicación de consola anterior, obtenemos el resultado correcto esperado:
fuente
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:
En mi caso, el tipo U es siempre una colección observable que almacena objetos del 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
Luego llame al GetMethod para encontrar su función genérica:
Hasta ahora, la llamada anterior es bastante idéntica a lo que se explicó anteriormente, pero con una pequeña diferencia cuando necesita pasarle múltiples parámetros.
Debe pasar una matriz Tipo [] a la función MakeGenericMethod que contiene los tipos de objetos "ficticios" que se crearon anteriormente:
Una vez hecho esto, debe llamar al método Invoke como se mencionó anteriormente.
Y tu estas listo. Funciona un encanto!
ACTUALIZAR:
Como resaltó @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:
myClassInfo contiene 2 propiedades de tipo
Type
que configuré 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 usaré en MakeGenericMethod.Gracias de nuevo por destacar este @Bevan.
fuente
MakeGenericMethod()
tener la palabra clave params para que no necesite crear una matriz; ni necesita crear instancias para obtener los tipos,methodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject))
sería suficiente.Inspirado por la respuesta de Enigmativity : supongamos que tiene dos (o más) clases, como
y desea llamar al método
Foo<T>
conBar
ySquare
, que se declara comoLuego puede implementar un método de extensión como:
Con esto, simplemente puede invocar
Foo
como:que funciona para cada clase En este caso, generará:
fuente