c# - net - ¿Cómo uso la reflexión para invocar un método privado?
reflection c# español (11)
Hay un grupo de métodos privados en mi clase, y necesito llamar a uno de forma dinámica según un valor de entrada. Tanto el código de invocación como los métodos de destino están en la misma instancia. El código se ve así:
MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType);
dynMethod.Invoke(this, new object[] { methodParams });
En este caso, GetMethod()
no devolverá métodos privados. ¿Qué BindingFlags
debo proporcionar a GetMethod()
para que pueda localizar métodos privados?
Reflexión sobre miembros privados es errónea.
- Reflejos de roturas tipo seguridad. Puedes intentar invocar un método que ya no existe (más), o con los parámetros incorrectos, o con demasiados parámetros, o no lo suficiente ... o incluso en el orden incorrecto (este es mi favorito :)). Por cierto, el tipo de retorno podría cambiar también.
- La reflexión es lenta.
La reflexión de los miembros privados rompe el principio de encapsulation y, por lo tanto, expone su código a lo siguiente:
- Aumente la complejidad de su código porque tiene que manejar el comportamiento interno de las clases. Lo que está oculto debe permanecer oculto.
- Hace que su código sea fácil de descifrar, ya que se compilará pero no se ejecutará si el método cambia su nombre.
- Hace que el código privado sea fácil de descifrar porque, si es privado, no debe llamarse así. Tal vez el método privado espera algún estado interior antes de ser llamado.
¿Y si debo hacerlo de todos modos?
Hay casos en los que, cuando dependes de un tercero o necesitas una API no expuesta, tienes que hacer alguna reflexión. Algunos también lo usan para probar algunas clases que poseen, pero que no quieren cambiar la interfaz para dar acceso a los miembros internos solo para las pruebas.
Si lo haces, hazlo bien.
- Mitigar lo fácil de romper:
Para mitigar el problema fácil de romper, lo mejor es detectar cualquier ruptura potencial mediante pruebas en pruebas unitarias que se ejecutarían en una compilación de integración continua o similar. Por supuesto, significa que siempre usa el mismo ensamblaje (que contiene los miembros privados). Si usas una carga dinámica y reflexión, te gusta jugar con fuego, pero siempre puedes atrapar la excepción que puede producir la llamada.
- Mitigar la lentitud de la reflexión:
En las versiones recientes de .Net Framework, CreateDelegate venció por un factor 50 que invoca MethodInfo:
// The following should be done once since this does some reflection
var method = this.GetType().GetMethod("Draw_" + itemType,
BindingFlags.NonPublic | BindingFlags.Instance);
// Here we create a Func that targets the instance of type which has the
// Draw_ItemType method
var draw = (Func<TInput, Output[]>)_method.CreateDelegate(
typeof(Func<TInput, TOutput[]>), this);
draw
llamadas de MethodInfo.Invoke
serán alrededor de MethodInfo.Invoke
más rápidas que las de MethodInfo.Invoke
uso de MethodInfo.Invoke
como un Func
estándar como ese:
var res = draw(methodParams);
Revisa esta publicación mía para ver el punto de referencia en diferentes invocaciones de métodos.
¿Está absolutamente seguro de que esto no se puede hacer a través de la herencia? La reflexión es lo último que debe mirar cuando resuelve un problema, hace que la refactorización, la comprensión de su código y cualquier análisis automatizado sean más difíciles.
Parece que deberías tener una clase DrawItem1, DrawItem2, etc. que anule tu dynMethod.
¿No podría simplemente tener un método de dibujo diferente para cada tipo que quiera dibujar? A continuación, llame al método Draw sobrecargado que pasa en el objeto de tipo itemType que se va a dibujar.
Su pregunta no aclara si itemType realmente se refiere a objetos de diferentes tipos.
Creo que puedes pasarlo a BindingFlags.NonPublic
donde está el método GetMethod
.
Invoca cualquier método a pesar de su nivel de protección en la instancia de objeto. ¡Disfrutar!
public static object InvokeMethod(object obj, string methodName, params object[] methodParams)
{
var methodParamTypes = methodParams?.Select(p => p.GetType()).ToArray() ?? new Type[] { };
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
MethodInfo method = null;
var type = obj.GetType();
while (method == null && type != null)
{
method = type.GetMethod(methodName, bindingFlags, Type.DefaultBinder, methodParamTypes, null);
type = type.BaseType;
}
return method?.Invoke(obj, methodParams);
}
Lea esta respuesta (suplementaria) (que a veces es la respuesta) para comprender a dónde va esto y por qué algunas personas en este hilo se quejan de que "todavía no funciona"
Escribí exactamente el mismo código que una de las respuestas aquí . Pero todavía tenía un problema. Puse un punto de ruptura en
var mi = o.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance );
Se ejecutó pero mi == null
Y continuó con un comportamiento como este hasta que lo "reconstruí" en todos los proyectos involucrados. Estaba probando la unidad de un conjunto mientras que el método de reflexión estaba sentado en el tercer conjunto. Fue totalmente confuso, pero utilicé Immediate Window para descubrir métodos y descubrí que un método privado que intenté probar por unidad tenía un nombre antiguo (lo renombré). Esto me dijo que el ensamblaje antiguo o el PDB todavía existe, incluso si el proyecto de prueba de unidad se construye, por alguna razón, el proyecto que las pruebas no construyó. "reconstruir" funcionó
Microsoft recientemente modificó la API de reflexión haciendo que la mayoría de estas respuestas queden obsoletas. Lo siguiente debería funcionar en plataformas modernas (incluyendo Xamarin.Forms y UWP):
obj.GetType().GetTypeInfo().GetDeclaredMethod("MethodName").Invoke(obj, yourArgsHere);
O como un método de extensión:
public static object InvokeMethod<T>(this T obj, string methodName, params object[] args)
{
var type = typeof(T);
var method = type.GetTypeInfo().GetDeclaredMethod(methodName);
return method.Invoke(obj, args);
}
Nota:
Si el método deseado está en una superclase de
obj
el genéricoT
debe establecerse explícitamente en el tipo de superclase.Si el método es asíncrono, puede usar
await (Task) obj.InvokeMethod(…)
.
Simplemente cambie su código para usar la versión sobrecargada de GetMethod
que acepta BindingFlags:
MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType,
BindingFlags.NonPublic | BindingFlags.Instance);
dynMethod.Invoke(this, new object[] { methodParams });
Aquí está la documentación de la enumeración BindingFlags .
Y si realmente quieres meterte en problemas, haz que sea más fácil de ejecutar escribiendo un método de extensión:
static class AccessExtensions
{
public static object call(this object o, string methodName, params object[] args)
{
var mi = o.GetType ().GetMethod (methodName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
if (mi != null) {
return mi.Invoke (o, args);
}
return null;
}
}
Y uso:
class Counter
{
public int count { get; private set; }
void incr(int value) { count += value; }
}
[Test]
public void making_questionable_life_choices()
{
Counter c = new Counter ();
c.call ("incr", 2); // "incr" is private !
c.call ("incr", 3);
Assert.AreEqual (5, c.count);
}
BindingFlags.NonPublic
BindingFlags.NonPublic
no devolverá ningún resultado por sí mismo. Como resultado, combinarlo con BindingFlags.Instance
hace el truco.
MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType,
BindingFlags.NonPublic | BindingFlags.Instance);