¿Cómo uso la reflexión para invocar un método privado?

326

Hay un grupo de métodos privados en mi clase, y necesito llamar a uno dinámicamente en función de 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. ¿ BindingFlagsQué necesito suministrar para GetMethod()que pueda localizar métodos privados?

Jeromy Irvine
fuente

Respuestas:

498

Simplemente cambie su código para usar la versiónGetMethod sobrecargada 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 enumeración de BindingFlags .

wprl
fuente
248
Me voy a meter en muchos problemas con esto.
Frank Schwieterman
1
BindingFlags.NonPublicprivatemétodo de no devolución .. :(
Moumit
44
@MoumitMondal ¿sus métodos son estáticos? Tiene que especificar BindingFlags.Instance, así como BindingFlags.NonPublicpara los métodos no estáticos.
BrianS
No @BrianS ... el método es non-staticy privatey la clase se hereda de System.Web.UI.Page... me hace tonto de todos modos ... no encontré la razón ... :(
Moumit
3
Agregar BindingFlags.FlattenHierarchyle permitirá obtener métodos de clases primarias para su instancia.
Dragonthoughts
67

BindingFlags.NonPublicno devolverá ningún resultado por sí solo. Resulta que combinarlo con BindingFlags.Instancehace el truco.

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
Jeromy Irvine
fuente
La misma lógica se aplica a las internalfunciones también
supertopi
Tengo un problema similar. ¿Qué sucede si "esto" es una clase secundaria e intenta invocar el método privado de los padres?
persianLife
¿Es posible usar esto para invocar los métodos protegidos de la clase base.base?
Shiv
51

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);
    }
cod3monk3y
fuente
14
¿Peligroso? Si. Pero una gran extensión auxiliar cuando está envuelta en el espacio de nombres de prueba de mi unidad. Gracias por esto.
Robert Wahler
55
Si le importan las excepciones reales lanzadas desde el método llamado, es una buena idea incluirlo en el bloque try try y volver a lanzar la excepción interna cuando se detecta TargetInvokationException. Lo hago en mi extensión auxiliar de prueba de unidad.
Slobodan Savkovic
2
Reflexión peligrosa? Hmmm ... C #, Java, Python ... en realidad todo es peligroso, incluso el mundo: D Solo tienes que tener cuidado de cómo hacerlo de manera segura ...
Leyendas
16

Microsoft modificó recientemente la API de reflexión haciendo que la mayoría de estas respuestas sean obsoletas. Lo siguiente debería funcionar en plataformas modernas (incluidos 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 objla Tgenérico se debe establecer explícitamente al tipo de la superclase.

  • Si el método es asíncrono, puede usarlo await (Task) obj.InvokeMethod(…).

Owen James
fuente
No funciona para la versión .net de UWP al menos porque funciona solo para métodos públicos : " Devuelve una colección que contiene todos los métodos públicos declarados en el tipo actual que coinciden con el nombre especificado ".
Dmytro Bondarenko
1
@DmytroBondarenko Lo probé con métodos privados y funcionó. Sin embargo, sí vi eso. No estoy seguro de por qué se comporta de manera diferente a la documentación, pero al menos funciona.
Owen James
Sí, no consideraría obsoletas todas las demás respuestas, si la documentación dice que GetDeclareMethod()está destinado a ser utilizado para recuperar solo un método público.
Mass Dot Net
10

¿Estás absolutamente seguro de que esto no se puede hacer a través de la herencia? La reflexión es lo último que debe tener en cuenta al resolver un problema, dificulta la refactorización, la comprensión de su código y cualquier análisis automatizado.

Parece que solo debería tener una clase DrawItem1, DrawItem2, etc. que anule su dynMethod.

Bill K
fuente
1
@ Bill K: Dadas otras circunstancias, decidimos no usar la herencia para esto, de ahí el uso de la reflexión. Para la mayoría de los casos, lo haríamos de esa manera.
Jeromy Irvine
8

La reflexión especialmente sobre los miembros privados es incorrecta

  • Reflexión rompe tipo seguridad. Puede intentar invocar un método que ya no existe (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 también podría cambiar.
  • La reflexión es lenta.

La reflexión de miembros privados rompe el principio de encapsulación 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 romper ya que se compilará pero no se ejecutará si el método cambió su nombre.
  • Hace que el código privado sea fácil de descifrar porque si es privado no está destinado a llamarse de esa manera. Tal vez el método privado espera algún estado interno antes de ser llamado.

¿Qué pasa si debo hacerlo de todos modos?

Hay casos en los que, cuando depende de un tercero o necesita alguna API no expuesta, debe reflexionar. 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 probando 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 ensamblado (que contiene los miembros privados). Si usa una carga dinámica y una reflexión, le gusta jugar con fuego, pero siempre puede detectar la Excepción que puede producir la llamada.

  • Mitigar la lentitud de la reflexión:

En las versiones recientes de .Net Framework, CreateDelegate supera en un factor 50 la invocación 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);

drawlas llamadas serán aproximadamente 50 veces más rápidas que el MethodInfo.Invoke uso drawcomo un estándar Funccomo ese:

var res = draw(methodParams);

Consulte esta publicación mía para ver el punto de referencia sobre invocaciones de diferentes métodos

Fabuloso
fuente
1
Aunque obtengo la inyección de dependencia debería ser una forma preferida de pruebas unitarias, supongo que usarlo con precaución no es totalmente malo usar la reflexión para acceder a las unidades que de otro modo serían inaccesibles para las pruebas. Personalmente, creo que además de los modificadores normales [públicos] [protegidos] [privados] también deberíamos tener modificadores [Prueba] [Composición] para que sea posible tener ciertas cosas visibles durante estas etapas sin estar obligados a hacer que todo sea público ( y por lo tanto tengo que documentar completamente esos métodos)
Andrew Paté
1
Gracias Fab por enumerar los problemas de reflexionar sobre los miembros privados, me ha llevado a revisar cómo me siento al usarlo y llegué a una conclusión ... Usar el reflejo para probar sus miembros privados es incorrecto, sin embargo, dejar las rutas de código sin probar es muy muy mal
andrew pate
2
Pero cuando se trata de pruebas unitarias de código heredado generalmente no comprobable por unidades, es una forma brillante de hacerlo
TS
2

¿No podría simplemente tener un método de dibujo diferente para cada tipo que desea dibujar? Luego llame al método Draw sobrecargado pasando el objeto de tipo itemType para dibujar.

Su pregunta no deja en claro si itemType realmente se refiere a objetos de diferentes tipos.

Peter Hession
fuente
1

Creo que se puede pasar que BindingFlags.NonPubliccuando se es el GetMethodmétodo.

Armin Ronacher
fuente
1

Invoca cualquier método a pesar de su nivel de protección en la instancia del 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);
}
Maksim Shamihulau
fuente
0

Lea esta respuesta (complementaria) (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 aún tenía un problema. Puse un punto de quiebre en

var mi = o.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance );

Se ejecutó pero mi == null

Y continuó con este comportamiento hasta que "reconstruí" todos los proyectos involucrados. Estaba probando un ensamblaje mientras el método de reflexión estaba en el tercer ensamblaje. Fue totalmente confuso, pero utilicé la ventana Inmediato para descubrir métodos y descubrí que un método privado que intenté probar en la unidad tenía un nombre antiguo (lo cambié de nombre). Esto me dijo que el antiguo ensamblaje o PDB todavía está disponible, incluso si el proyecto de prueba de unidad se construye, por alguna razón el proyecto que prueba no se construyó. "reconstruir" funcionó

TS
fuente
-1

BindingFlags.NonPublic

Khoth
fuente