Comment utiliser la réflexion pour invoquer une méthode privée?

326

Il y a un groupe de méthodes privées dans ma classe, et je dois en appeler une dynamiquement en fonction d'une valeur d'entrée. Le code appelant et les méthodes cible se trouvent dans la même instance. Le code ressemble à ceci:

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType);
dynMethod.Invoke(this, new object[] { methodParams });

Dans ce cas, GetMethod()ne renverra pas de méthodes privées. Que BindingFlagsdois-je fournir pour GetMethod()qu'il puisse localiser des méthodes privées?

Jeromy Irvine
la source

Réponses:

498

Modifiez simplement votre code pour utiliser la versionGetMethod surchargée qui accepte BindingFlags:

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
dynMethod.Invoke(this, new object[] { methodParams });

Voici la documentation d'énumération BindingFlags .

wprl
la source
248
Je vais me mettre tellement dans le pétrin avec ça.
Frank Schwieterman
1
BindingFlags.NonPublicprivateméthode de non retour .. :(
Moumit
4
@MoumitMondal vos méthodes sont-elles statiques? Vous devez spécifier BindingFlags.Instanceainsi que BindingFlags.NonPublicpour les méthodes non statiques.
BrianS
Non @BrianS .. la méthode est non-staticet privateet la classe est héritée de System.Web.UI.Page.. ça me fait quand même duper .. je n'ai pas trouvé la raison .. :(
Moumit
3
L'ajout BindingFlags.FlattenHierarchyvous permettra d'obtenir des méthodes des classes parentes sur votre instance.
Dragonthoughts
67

BindingFlags.NonPublicne retournera aucun résultat par lui-même. Il s'avère que le combiner avec BindingFlags.Instancefait l'affaire.

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
Jeromy Irvine
la source
La même logique s'applique également aux internalfonctions
supertopi
J'ai le même problème. Que faire si "this" est une classe enfant et que vous essayez d'invoquer la méthode privée du parent?
persianLife
Est-il possible de l'utiliser pour appeler des méthodes protégées par la classe base.base?
Shiv
51

Et si vous voulez vraiment vous mettre en difficulté, rendez-le plus facile à exécuter en écrivant une méthode d'extension:

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

Et l'utilisation:

    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
la source
14
Dangereux? Oui. Mais une grande extension d'aide lorsqu'elle est enveloppée dans mon espace de noms de test unitaire. Merci pour cela.
Robert Wahler
5
Si vous vous souciez des vraies exceptions levées à partir de la méthode appelée, c'est une bonne idée de l'envelopper dans le bloc try catch et de relancer l'exception interne à la place lorsque TargetInvokationException est interceptée. Je le fais dans mon extension d'aide de test unitaire.
Slobodan Savkovic
2
Réflexion dangereuse? Hmmm ... C #, Java, Python ... en fait, tout est dangereux, même le monde: D Il suffit de faire attention à la façon de le faire en toute sécurité ...
Legends
16

Microsoft a récemment modifié l'API de réflexion, rendant la plupart de ces réponses obsolètes. Les éléments suivants devraient fonctionner sur les plates-formes modernes (y compris Xamarin.Forms et UWP):

obj.GetType().GetTypeInfo().GetDeclaredMethod("MethodName").Invoke(obj, yourArgsHere);

Ou comme méthode d'extension:

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

Remarque:

  • Si la méthode souhaitée se trouve dans une superclasse, objle Tgénérique doit être explicitement défini sur le type de la superclasse.

  • Si la méthode est asynchrone, vous pouvez l'utiliser await (Task) obj.InvokeMethod(…).

Owen James
la source
Cela ne fonctionne pas pour la version UWP .net au moins parce qu'il ne fonctionne que pour les méthodes publiques : " Renvoie une collection qui contient toutes les méthodes publiques déclarées sur le type actuel qui correspondent au nom spécifié ".
Dmytro Bondarenko
1
@DmytroBondarenko Je l'ai testé contre des méthodes privées et cela a fonctionné. Mais je l'ai vu. Je ne sais pas pourquoi il se comporte différemment de la documentation, mais au moins cela fonctionne.
Owen James
Ouais, je n'appellerais pas toutes les autres réponses obsolètes, si la documentation dit GetDeclareMethod()est destinée à être utilisée pour récupérer uniquement une méthode publique.
Mass Dot Net
10

Êtes-vous absolument sûr que cela ne peut pas se faire par héritage? La réflexion est la toute dernière chose à considérer lors de la résolution d'un problème, elle rend la refactorisation, la compréhension de votre code et toute analyse automatisée plus difficiles.

Il semble que vous devriez simplement avoir une classe DrawItem1, DrawItem2, etc. qui remplace votre dynMethod.

Bill K
la source
1
@Bill K: Dans d'autres circonstances, nous avons décidé de ne pas utiliser l'héritage pour cela, d'où l'utilisation de la réflexion. Dans la plupart des cas, nous le ferions de cette façon.
Jeromy Irvine
8

La réflexion en particulier sur les membres privés est fausse

  • La réflexion rompt la sécurité du type. Vous pouvez essayer d'invoquer une méthode qui n'existe plus (ou plus), ou avec les mauvais paramètres, ou avec trop de paramètres, ou pas assez ... ou même dans le mauvais ordre (celui-ci mon préféré :)). Par ailleurs, le type de retour pourrait également changer.
  • La réflexion est lente.

La réflexion des membres privés rompt le principe d' encapsulation et expose ainsi votre code aux éléments suivants:

  • Augmentez la complexité de votre code car il doit gérer le comportement interne des classes. Ce qui est caché doit rester caché.
  • Rend votre code facile à casser car il se compilera mais ne s'exécutera pas si la méthode a changé de nom.
  • Rend le code privé facile à casser car s'il est privé, il n'est pas destiné à être appelé de cette façon. Peut-être que la méthode privée attend un état intérieur avant d'être appelée.

Et si je dois quand même le faire?

Il y a donc des cas, lorsque vous dépendez d'un tiers ou que vous avez besoin d'une API non exposée, vous devez faire quelques réflexions. Certains l'utilisent également pour tester certaines classes dont ils sont propriétaires mais qui ne veulent pas modifier l'interface pour donner accès aux membres internes uniquement pour les tests.

Si vous le faites, faites-le bien

  • Atténuer la facilité de rupture:

Pour atténuer le problème facile à casser, le mieux est de détecter toute rupture potentielle en testant des tests unitaires qui s'exécuteraient dans une version d'intégration continue ou autre. Bien sûr, cela signifie que vous utilisez toujours le même assembly (qui contient les membres privés). Si vous utilisez une charge dynamique et une réflexion, vous aimez jouer avec le feu, mais vous pouvez toujours attraper l'exception que l'appel peut produire.

  • Atténuez la lenteur de la réflexion:

Dans les versions récentes de .Net Framework, CreateDelegate battait d'un facteur 50 que MethodInfo invoquait:

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

drawles appels seront environ 50 fois plus rapides que l' MethodInfo.Invoke utilisation drawcomme standard Funccomme ça:

var res = draw(methodParams);

Consultez ce post pour voir des références sur différentes invocations de méthodes

Fab
la source
1
Bien que l'injection de dépendances soit une méthode de test unitaire préférée, je suppose qu'il n'est pas totalement mauvais d'utiliser la réflexion pour accéder à des unités qui seraient autrement inaccessibles pour le test. Personnellement, je pense qu'en plus des modificateurs [publics] [protégés] [privés] normaux, nous devrions également avoir des modificateurs [Test] [Composition] afin qu'il soit possible d'avoir certaines choses visibles pendant ces étapes sans être forcé de tout rendre public ( et doivent donc documenter pleinement ces méthodes)
andrew pate
1
Merci Fab d'avoir énuméré les problèmes de réflexion sur les membres privés, cela m'a amené à revoir ce que je ressens à propos de son utilisation et est arrivé à une conclusion ... vraiment vraiment mal.
andrew pate
2
Mais en ce qui concerne les tests unitaires de code hérité généralement non testable à l'unité, c'est une manière brillante de le faire
TS
2

Ne pourriez-vous pas simplement avoir une méthode Draw différente pour chaque type que vous souhaitez dessiner? Appelez ensuite la méthode Draw surchargée en passant l'objet de type itemType à dessiner.

Votre question ne précise pas si itemType fait réellement référence à des objets de types différents.

Peter Hession
la source
1

Je pense que vous pouvez le passer là BindingFlags.NonPublicc'est la GetMethodméthode.

Armin Ronacher
la source
1

Appelle n'importe quelle méthode malgré son niveau de protection sur l'instance d'objet. Prendre plaisir!

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
la source
0

Lisez cette réponse (supplémentaire) (qui est parfois la réponse) pour comprendre où cela va et pourquoi certaines personnes dans ce fil se plaignent que "cela ne fonctionne toujours pas"

J'ai écrit exactement le même code que l'une des réponses ici . Mais j'avais toujours un problème. J'ai placé un point d'arrêt sur

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

Il a exécuté mais mi == null

Et cela a continué comme ça jusqu'à ce que je "reconstruise" tous les projets impliqués. J'étais en train de tester un assemblage tandis que la méthode de réflexion était assise dans le troisième assemblage. C'était totalement déroutant, mais j'ai utilisé la fenêtre immédiate pour découvrir les méthodes et j'ai trouvé qu'une méthode privée que j'essayais de tester par unité avait un ancien nom (je l'ai renommé). Cela m'a dit que l'ancien assemblage ou PDB est toujours là même si le projet de test unitaire se construit - pour une raison quelconque, le projet qu'il n'a pas construit. "reconstruire" a fonctionné

TS
la source