web-dev-qa-db-fra.com

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

Il existe un groupe de méthodes privées dans ma classe et je dois en appeler une de manière dynamique 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. Quelle BindingFlags dois-je fournir à GetMethod() afin qu'il puisse localiser des méthodes privées?

289
Jeromy Irvine

Changez simplement votre code pour utiliser la version surchargée de GetMethod 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 .

455
wprl

BindingFlags.NonPublic ne retournera aucun résultat par lui-même. En fin de compte, le combiner avec BindingFlags.Instance fait l'affaire.

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
64
Jeromy Irvine

Et si vous vraiment voulez vous créer des problèmes, facilitez-le à l’exécution 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 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);
    }
47
cod3monk3y

Microsoft a récemment modifié l'API de réflexion rendant la plupart de ces réponses obsolètes. Ce qui suit devrait 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 est dans une superclasse de obj, le T generic doit être explicitement défini sur le type de la superclasse. 

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

13
Owen James

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

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

11
Bill K

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

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

La réflexion des membres privés rompt encapsulation le principe 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 à décomposer car il compilera mais ne fonctionnera 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 interne avant d'être appelée.

Et si je dois le faire quand même?

Dans certains cas, lorsque vous dépendez d'un tiers ou que vous avez besoin d'une API non exposée, vous devez réfléchir. Certains l'utilisent également pour tester certaines classes qu'ils possèdent mais ils ne veulent pas modifier l'interface pour donner accès aux membres internes uniquement pour des tests.

Si vous le faites, faites-le bien

  • Atténuer le facile à casser:

Pour atténuer ce problème, le mieux est de détecter toute rupture potentielle en effectuant des tests unitaires qui s'exécuteraient dans une version à intégration continue ou similaire. Bien sûr, cela signifie que vous utilisez toujours la même Assemblée (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énuer la lenteur de la réflexion:

Dans les versions récentes de .Net Framework, CreateDelegate a battu d'un facteur 50 l'invocation 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);

Les appels draw seront environ 50 fois plus rapides que MethodInfo.Invoke utilisez draw en tant que Func standard comme celui-ci:

var res = draw(methodParams);

Cochez cette poste de la mienne pour voir le point de repère sur différentes invocations de méthode

5
Fab

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 n'indique pas clairement si itemType fait véritablement référence à des objets de types différents.

2
Peter Hession

Je pense que vous pouvez le transmettre BindingFlags.NonPublic it est la méthode GetMethod.

1
Armin Ronacher

Invoque 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);
}
1
Maksim Shamihulau

BindingFlags.NonPublic

0
Khoth

Lisez cette réponse (supplémentaire) (qui est parfois la réponse) pour comprendre où cela se passe 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é le point de rupture 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 une assemblée alors que la méthode de réflexion reposait dans la troisième assemblée. C'était totalement déroutant, mais j'ai utilisé Immediate Window pour découvrir des méthodes et j'ai découvert qu'une méthode privée que j'avais essayée de tester à l'unité avait l'ancien nom (je l'ai renommée). Cela m'a permis de constater que l'ancien assemblage ou PDB existe toujours, même si le projet de test unitaire est construit - pour une raison quelconque, le projet que les tests n'ont pas construit. "reconstruire" a travaillé

0
T.S.