it-swarm.com.de

Wie verwende ich Reflection, um eine private Methode aufzurufen?

Es gibt eine Gruppe privater Methoden in meiner Klasse, die ich dynamisch basierend auf einem Eingabewert aufrufen muss. Sowohl der aufrufende Code als auch die Zielmethode befinden sich in derselben Instanz. Der Code sieht so aus:

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

In diesem Fall gibt GetMethod() keine privaten Methoden zurück. Welche BindingFlags muss ich an GetMethod() angeben, damit private Methoden gefunden werden können?

289
Jeromy Irvine

Ändern Sie einfach Ihren Code, um die überladene Version von GetMethod zu verwenden, die BindingFlags akzeptiert:

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

Hier ist die BindingFlags-Enumeration-Dokumentation .

455
wprl

BindingFlags.NonPublic gibt selbst keine Ergebnisse zurück. Die Kombination mit BindingFlags.Instance führt den Trick aus.

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

Und wenn Sie wirklich sich selbst in Schwierigkeiten bringen möchten, erleichtern Sie die Ausführung, indem Sie eine Erweiterungsmethode schreiben:

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

Und Verwendung:

    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 hat vor kurzem die Reflection-API geändert die meisten dieser Antworten sind veraltet. Folgendes sollte auf modernen Plattformen (einschließlich Xamarin.Forms und UWP) funktionieren:

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

Oder als Erweiterungsmethode:

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

Hinweis:

  • Wenn sich die gewünschte Methode in einer Oberklasse von obj befindet, muss der Generic T explizit auf den Typ der Oberklasse gesetzt werden. 

  • Wenn die Methode asynchron ist, können Sie await (Task) obj.InvokeMethod(…) verwenden.

13
Owen James

Sind Sie absolut sicher, dass dies nicht durch Vererbung erfolgen kann? Überlegungen sind das letzte, was Sie bei der Lösung eines Problems beachten sollten. Dies macht das Refactoring, das Verstehen Ihres Codes und jede automatisierte Analyse schwieriger.

Anscheinend sollten Sie nur eine DrawItem1-, DrawItem2 usw.-Klasse haben, die Ihre dynMethod überschreibt.

11
Bill K

Das Nachdenken vor allem über private Mitglieder ist falsch

  • Reflexion bricht die Typensicherheit. Sie können versuchen, eine Methode aufzurufen, die nicht mehr existiert (oder nicht mehr vorhanden ist), oder mit den falschen Parametern oder mit zu vielen Parametern oder nicht genug ... oder sogar in der falschen Reihenfolge (dies ist mein Favorit :)) . Übrigens könnte sich auch der Rückgabetyp ändern.
  • Die Reflexion ist langsam.

Private Members Reflection Breaks Encapsulation Prinzip und macht Ihren Code daher den folgenden Informationen zugänglich:

  • Erhöhen Sie die Komplexität Ihres Codes, da er sich mit dem inneren Verhalten der Klassen befassen muss. Was verborgen ist, sollte verborgen bleiben.
  • Ermöglicht die Aufhebung Ihres Codes, da er kompiliert wird, jedoch nicht ausgeführt wird, wenn die Methode ihren Namen geändert hat.
  • Erlaubt es, den privaten Code leicht aufzulösen, da er nicht so ist, wenn er privat ist. Vielleicht erwartet die private Methode einen inneren Zustand, bevor sie aufgerufen wird.

Was ist, wenn ich es trotzdem tun muss?

Es gibt Fälle, in denen Sie von einer dritten Partei abhängig sind oder ein nicht offengelegtes API benötigen, müssen Sie darüber nachdenken. Einige verwenden es auch, um einige Klassen zu testen, die sie besitzen, aber sie möchten die Schnittstelle nicht ändern, um nur für Tests Zugriff auf die inneren Mitglieder zu erhalten.

Wenn Sie es tun, machen Sie es richtig

  • Mildern Sie die leicht zu brechen:

Um das Problem zu lösen, das leicht zu brechen ist, sollten Sie potenzielle Brüche am besten durch Testen von Komponententests erkennen, die in einem kontinuierlichen Integrations-Build oder ähnlichem ausgeführt werden. Natürlich bedeutet dies, dass Sie immer dieselbe Assembly (die die privaten Mitglieder enthält) verwenden. Wenn Sie eine dynamische Last und Reflexion verwenden, spielen Sie gerne mit dem Feuer, aber Sie können immer die Ausnahme erkennen, die der Anruf erzeugt.

  • Verringern Sie die Langsamkeit der Reflexion:

In den aktuellen Versionen von .Net Framework schlägt CreateDelegate um den Faktor 50, den MethodInfo aufruft: 

// 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-Aufrufe sind etwa 50-mal schneller als MethodInfo.Invoke Verwenden Sie draw als Standard Func wie folgt:

var res = draw(methodParams);

Überprüfen Sie diese post von mir , um Benchmark für verschiedene Methodenaufrufe zu sehen

5
Fab

Könnten Sie nicht für jeden Typ, den Sie zeichnen möchten, eine andere Draw-Methode verwenden? Rufen Sie dann die überladene Draw-Methode auf, indem Sie das zu zeichnende Objekt vom Typ itemType übergeben.

Ihre Frage macht nicht klar, ob itemType wirklich auf Objekte unterschiedlichen Typs verweist.

2
Peter Hession

Ich denke, Sie können es BindingFlags.NonPublic übergeben, wobei it die GetMethod-Methode ist.

1
Armin Ronacher

Ruft eine beliebige Methode trotz ihrer Schutzstufe für die Objektinstanz auf. Genießen!

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

BindingFlags.NonPublic

0
Khoth

Lies diese (ergänzende) Antwort (das ist manchmal die Antwort), um zu verstehen, wohin das geht und warum einige Leute in diesem Thread beschweren sich, dass "es funktioniert immer noch nicht"

Ich habe genau den gleichen Code wie eine der Antworten hier geschrieben . Aber ich hatte immer noch ein Problem. Ich habe einen Haltepunkt gesetzt 

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

Es wurde ausgeführt, aber mi == null

Und das Verhalten hielt an, bis ich alle beteiligten Projekte "neu aufgebaut" habe. Ich habe gerade eine Baugruppe getestet, während die Reflexionsmethode in der dritten Baugruppe saß. Es war total verwirrend, aber ich verwendete sofortiges Fenster, um Methoden zu entdecken, und ich fand heraus, dass eine private Methode, die ich versuchte, Tests zu machen, einen alten Namen hatte (ich habe ihn umbenannt). Dies sagte mir, dass alte Assembly oder PDB immer noch da draußen sind, auch wenn ein Unit-Test-Projekt erstellt wird - aus irgendeinem Grund wurden die Tests nicht gebaut. "Wiederaufbau" hat funktioniert

0
T.S.