Wie verwende ich Reflektion, um eine private Methode aufzurufen?

325

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

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

In diesem Fall GetMethod()werden keine privaten Methoden zurückgegeben. Was BindingFlagsmuss ich bereitstellen, GetMethod()damit private Methoden gefunden werden können?

Jeromy Irvine
quelle

Antworten:

497

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

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

Hier ist die Dokumentation zur BindingFlags-Aufzählung .

wprl
quelle
248
Ich werde mich damit in so große Schwierigkeiten bringen.
Frank Schwieterman
1
BindingFlags.NonPublicprivateMethode nicht zurückgeben .. :(
Moumit
4
@ MoumitMondal sind Ihre Methoden statisch? Sie müssen BindingFlags.Instancesowie BindingFlags.NonPublicfür nicht statische Methoden angeben .
BrianS
Nein @BrianS .. die Methode ist non-staticund privateund die Klasse wird von geerbt System.Web.UI.Page.. es macht mich sowieso zum Narren .. ich habe den Grund nicht gefunden .. :(
Moumit
3
Durch Hinzufügen BindingFlags.FlattenHierarchykönnen Sie Methoden aus übergeordneten Klassen zu Ihrer Instanz abrufen.
Drachen Gedanken
67

BindingFlags.NonPublicgibt keine Ergebnisse für sich zurück. Wie sich herausstellt, reicht es aus, es mit zu kombinieren BindingFlags.Instance.

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
Jeromy Irvine
quelle
Die gleiche Logik gilt auch für internalFunktionen
Supertopi
Ich habe ein ähnliches Problem. Was ist, wenn "dies" eine untergeordnete Klasse ist und Sie versuchen, die private Methode des Elternteils aufzurufen?
PersianLife
Ist es möglich, dies zum Aufrufen von geschützten Methoden der base.base-Klasse zu verwenden?
Shiv
51

Und wenn Sie wirklich in Schwierigkeiten geraten möchten, vereinfachen 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);
    }
cod3monk3y
quelle
14
Gefährlich? Ja. Aber eine großartige Helfer-Erweiterung, wenn sie in meinen Unit-Testing-Namespace eingeschlossen ist. Danke dafür.
Robert Wahler
5
Wenn Sie sich für echte Ausnahmen interessieren, die von der aufgerufenen Methode ausgelöst werden, ist es eine gute Idee, sie in den try catch-Block zu packen und stattdessen die innere Ausnahme erneut auszulösen, wenn TargetInvokationException abgefangen wird. Ich mache das in meiner Unit-Test-Helfer-Erweiterung.
Slobodan Savkovic
2
Reflexion gefährlich? Hmmm ... C #, Java, Python ... eigentlich ist alles gefährlich, sogar die Welt: D Man muss nur darauf achten, wie man es sicher macht ...
Legends
16

Microsoft hat kürzlich die Reflection-API geändert, sodass die meisten dieser Antworten veraltet sind. 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 die gewünschte Methode in einer Oberklasse von ist objder TGattungs ausdrücklich auf die Art der Oberklasse festgelegt werden muss.

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

Owen James
quelle
Es funktioniert nicht für die UWP .net-Version, zumindest weil es nur für öffentliche Methoden funktioniert : " Gibt eine Sammlung zurück, die alle öffentlichen Methoden enthält , die für den aktuellen Typ deklariert wurden und mit dem angegebenen Namen übereinstimmen. "
Dmytro Bondarenko
1
@DmytroBondarenko Ich habe es gegen private Methoden getestet und es hat funktioniert. Das habe ich jedoch gesehen. Ich bin mir nicht sicher, warum es sich anders verhält als die Dokumentation, aber es funktioniert zumindest.
Owen James
Ja, ich würde nicht alle anderen Antworten als veraltet bezeichnen, wenn in der Dokumentation angegeben GetDeclareMethod()ist, dass nur eine öffentliche Methode abgerufen werden soll.
Mass Dot Net
10

Sind Sie absolut sicher, dass dies nicht durch Vererbung möglich ist? Reflexion ist das allerletzte, worauf Sie bei der Lösung eines Problems achten sollten. Sie erschwert das Refactoring, das Verständnis Ihres Codes und jede automatisierte Analyse.

Es sieht so aus, als ob Sie nur eine DrawItem1-, DrawItem2- usw. Klasse haben sollten, die Ihre dynMethod überschreibt.

Bill K.
quelle
1
@ Bill K: Unter anderen Umständen haben wir beschlossen, hierfür keine Vererbung zu verwenden, daher die Verwendung von Reflexion. In den meisten Fällen würden wir das so machen.
Jeromy Irvine
8

Überlegungen insbesondere zu privaten Mitgliedern sind falsch

  • Reflexionsbrüche Typ Sicherheit. Sie können versuchen, eine Methode aufzurufen, die (nicht mehr) existiert oder mit den falschen Parametern oder mit zu vielen Parametern oder nicht genug ... oder sogar in der falschen Reihenfolge (diese ist meine Lieblingsmethode :)). Übrigens könnte sich auch der Rückgabetyp ändern.
  • Die Reflexion ist langsam.

Private Mitglieder Reflexion bricht Verkapselung Prinzip und damit Ihr Code wie folgt ausgesetzt wird :

  • Erhöhen Sie die Komplexität Ihres Codes, da er das innere Verhalten der Klassen verarbeiten muss. Was verborgen ist, sollte verborgen bleiben.
  • Erleichtert das Brechen Ihres Codes, da er kompiliert wird, aber nicht ausgeführt wird, wenn die Methode ihren Namen ändert.
  • Erleichtert das Brechen des privaten Codes, da er, wenn er privat ist, nicht so aufgerufen werden soll. 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, wenn Sie von einem Dritten abhängig sind oder eine nicht exponierte API benötigen, einige Überlegungen anstellen müssen. Einige verwenden es auch, um einige Klassen zu testen, die sie besitzen, aber dass sie die Schnittstelle nicht ändern möchten, um den inneren Mitgliedern nur für Tests Zugriff zu gewähren.

Wenn Sie es tun, machen Sie es richtig

  • Mildern Sie das leicht zu brechende:

Um das Problem der leicht zu brechenden Probleme zu verringern, ist es am besten, potenzielle Unterbrechungen durch Tests in Komponententests zu erkennen, die in einem kontinuierlichen Integrations-Build oder dergleichen ausgeführt werden. Dies bedeutet natürlich, dass Sie immer dieselbe Assembly verwenden (die die privaten Mitglieder enthält). Wenn Sie eine dynamische Last und Reflexion verwenden, spielen Sie gerne mit dem Feuer, aber Sie können immer die Ausnahme abfangen, die der Anruf möglicherweise erzeugt.

  • Mildern Sie die Langsamkeit der Reflexion:

In den neuesten Versionen von .Net Framework hat CreateDelegate die von MethodInfo aufgerufenen Faktoren um den Faktor 50 übertroffen:

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

drawAnrufe sind etwa 50-mal schneller als die MethodInfo.Invoke Verwendung drawals Standard Func:

var res = draw(methodParams);

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

Fab
quelle
1
Obwohl ich die Abhängigkeitsinjektion als bevorzugte Methode zum Testen von Einheiten erhalten sollte, ist es meiner Meinung nach nicht völlig böse, mit Reflexion auf Einheiten zuzugreifen, auf die sonst zum Testen nicht zugegriffen werden kann. Persönlich denke ich, dass wir neben den normalen [öffentlichen] [geschützten] [privaten] Modifikatoren auch Modifikatoren [Test] [Komposition] haben sollten, damit es möglich ist, bestimmte Dinge in diesen Phasen sichtbar zu machen, ohne gezwungen zu sein, alles vollständig öffentlich zu machen ( und müssen daher diese Methoden vollständig dokumentieren)
Andrew Pate
1
Vielen Dank an Fab für die Auflistung der Probleme beim Nachdenken über private Mitglieder. Es hat mich veranlasst, zu überprüfen, wie ich mit der Verwendung umgehe, und bin zu dem Schluss gekommen ... Die Verwendung von Reflexion zum Unit-Test Ihrer privaten Mitglieder ist falsch, jedoch ist es ungetestet, Codepfade ungetestet zu lassen wirklich wirklich falsch.
Andrew Pate
2
Aber wenn es um Unit-Tests von im Allgemeinen nicht Unit-testbarem Legacy-Code geht, ist dies eine hervorragende Möglichkeit
TS
2

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

Ihre Frage macht nicht klar, ob itemType wirklich auf Objekte unterschiedlicher Typen verweist.

Peter Hession
quelle
1

Ich denke, Sie können es BindingFlags.NonPublicdort weitergeben, wo es die GetMethodMethode ist.

Armin Ronacher
quelle
1

Ruft eine 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);
}
Maksim Shamihulau
quelle
0

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

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 aber ausgeführt mi == null

Und es ging so weiter, bis ich alle beteiligten Projekte "neu aufgebaut" habe. Ich habe eine Baugruppe getestet, während die Reflexionsmethode in der dritten Baugruppe saß. Es war total verwirrend, aber ich habe Immediate Window verwendet, um Methoden zu entdecken, und festgestellt, dass eine private Methode, die ich zum Unit-Test versuchte, einen alten Namen hatte (ich habe sie umbenannt). Dies sagte mir, dass alte Assemblys oder PDBs immer noch da draußen sind, selbst wenn Unit-Test-Projekte erstellt werden - aus irgendeinem Grund wurden Projekte, die getestet wurden, nicht erstellt. "Wiederaufbau" hat funktioniert

TS
quelle
-1

BindingFlags.NonPublic

Khoth
quelle