Jak użyć refleksji, aby wywołać metodę prywatną?

326

W mojej klasie jest grupa prywatnych metod i muszę wywoływać jedną dynamicznie na podstawie wartości wejściowej. Zarówno kod wywołujący, jak i metody docelowe znajdują się w tej samej instancji. Kod wygląda następująco:

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

W takim przypadku GetMethod()nie zwróci metod prywatnych. Co BindingFlagsmuszę dostarczyć, aby GetMethod()mógł znaleźć metody prywatne?

Jeromy Irvine
źródło

Odpowiedzi:

498

Wystarczy zmienić kod, aby użyć przeciążonej wersji,GetMethod która akceptuje BindingFlags:

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

Oto dokumentacja wyliczenia BindingFlags .

wprl
źródło
248
Mam w tym tyle kłopotów.
Frank Schwieterman,
1
BindingFlags.NonPublicnie zwraca privatemetody .. :(
Moumit
4
@MoumitMondal czy twoje metody są statyczne? Musisz określić BindingFlags.Instancetak samo, jak BindingFlags.NonPublicdla metod niestatycznych.
BrianS
Nie @BrianS .. metoda jest, non-statica privateklasa jest dziedziczona z System.Web.UI.Page.. to i tak mnie oszukuje .. nie znalazłem powodu .. :(
Moumit,
3
Dodanie BindingFlags.FlattenHierarchyumożliwi pobranie metod z klas nadrzędnych do instancji.
Dragonthoughts
67

BindingFlags.NonPublicsam nie zwróci żadnych wyników. Jak się okazuje, połączenie z tym BindingFlags.Instancerozwiązuje problem.

MethodInfo dynMethod = this.GetType().GetMethod("Draw_" + itemType, 
    BindingFlags.NonPublic | BindingFlags.Instance);
Jeromy Irvine
źródło
Ta sama logika dotyczy również internalfunkcji
supertopi,
Mam podobny problem. Co jeśli „to” jest klasą podrzędną i spróbujesz wywołać prywatną metodę rodzica?
persianLife
Czy można tego użyć do wywołania metod chronionych klasą base.base?
Shiv
51

A jeśli naprawdę chcesz wpaść w kłopoty, ułatw to wykonanie, pisząc metodę rozszerzenia:

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

I użycie:

    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
źródło
14
Niebezpieczny? Tak. Ale świetne rozszerzenie pomocnika, gdy jest owinięte w mojej przestrzeni nazw testów jednostki. Dzięki za to.
Robert Wahler,
5
Jeśli zależy Ci na prawdziwych wyjątkach zgłaszanych przez wywoływaną metodę, dobrym pomysłem jest zawinięcie jej w blok catch i powtórzenie wewnętrznego wyjątku, gdy złapany zostanie wyjątek TargetInvokationException. Robię to w moim rozszerzeniu pomocnika testu jednostkowego.
Slobodan Savkovic
2
Odbicie niebezpieczne? Hmmm ... C #, Java, Python ... właściwie wszystko jest niebezpieczne, nawet świat: D Musisz tylko zadbać o to, jak to zrobić bezpiecznie ...
Legends
16

Firma Microsoft niedawno zmodyfikowała interfejs API odbicia, przez co większość tych odpowiedzi stała się nieaktualna. Następujące elementy powinny działać na nowoczesnych platformach (w tym Xamarin.Forms i UWP):

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

Lub jako metoda rozszerzenia:

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

Uwaga:

  • Jeśli pożądany sposób w nadklasą ogólne muszą być wyraźnie określone w rodzaju nadrzędnej.objT

  • Jeśli metoda jest asynchroniczna, możesz użyć await (Task) obj.InvokeMethod(…).

Owen James
źródło
Nie działa dla wersji UWP .net przynajmniej dlatego, że działa tylko dla metod publicznych : „ Zwraca kolekcję, która zawiera wszystkie metody publiczne zadeklarowane dla bieżącego typu, które pasują do podanej nazwy ”.
Dmytro Bondarenko
1
@DmytroBondarenko Przetestowałem to pod kątem prywatnych metod i zadziałało. Widziałem to jednak. Nie jestem pewien, dlaczego zachowuje się inaczej niż w dokumentacji, ale przynajmniej działa.
Owen James,
Tak, nie nazwałbym wszystkich innych odpowiedzi przestarzałymi, jeśli dokumentacja mówi, że GetDeclareMethod()jest przeznaczony do użycia tylko do publicznej metody.
Mass Dot Net
10

Czy jesteś absolutnie pewien, że nie da się tego zrobić poprzez dziedziczenie? Refleksja to ostatnia rzecz, na którą powinieneś zwrócić uwagę przy rozwiązywaniu problemu. Utrudnia to refaktoryzację, zrozumienie kodu i automatyczną analizę.

Wygląda na to, że powinieneś mieć po prostu klasę DrawItem1, DrawItem2 itd., Która przesłania twoją metodę dynMethod.

Bill K.
źródło
1
@ Bill K: Biorąc pod uwagę inne okoliczności, postanowiliśmy nie używać do tego dziedziczenia, stąd zastosowanie refleksji. W większości przypadków zrobilibyśmy to w ten sposób.
Jeromy Irvine,
8

Refleksja szczególnie na temat członków prywatnych jest błędna

  • Odbicie przerywa bezpieczeństwo typu. Możesz spróbować wywołać metodę, która nie istnieje (już), albo z niewłaściwymi parametrami, albo ze zbyt dużą liczbą parametrów, albo za mało ... lub nawet w niewłaściwej kolejności (ta moja ulubiona :)). Nawiasem mówiąc, typ zwrotu może się również zmienić.
  • Odbicie jest powolne.

Odbicie członków prywatnych łamie zasadę enkapsulacji, a tym samym naraża Twój kod na:

  • Zwiększ złożoność swojego kodu, ponieważ musi on obsługiwać wewnętrzne zachowanie klas. To, co jest ukryte, powinno pozostać ukryte.
  • Ułatwia złamanie kodu, ponieważ będzie się kompilował, ale nie będzie działać, jeśli metoda zmieni nazwę.
  • Sprawia, że ​​kod prywatny jest łatwy do złamania, ponieważ jeśli jest prywatny, nie jest przeznaczony do takiego wywoływania. Być może metoda prywatna oczekuje jakiegoś stanu wewnętrznego przed wywołaniem.

Co jeśli i tak muszę to zrobić?

Zdarzają się przypadki, w których zależysz na stronie trzeciej lub potrzebujesz nieosłoniętego interfejsu API, musisz się zastanowić. Niektórzy używają go również do testowania posiadanych klas, ale nie chcą zmieniać interfejsu, aby dać dostęp do wewnętrznych elementów tylko na potrzeby testów.

Jeśli to zrobisz, zrób to dobrze

  • Łagodź łatwe do złamania:

Aby złagodzić problem łatwej do złamania, najlepiej jest wykryć potencjalną przerwę, testując w testach jednostkowych, które działałyby w kompilacji ciągłej lub podobnej. Oczywiście oznacza to, że zawsze używasz tego samego zestawu (który zawiera członków prywatnych). Jeśli używasz obciążenia dynamicznego i odbicia, lubisz bawić się ogniem, ale zawsze możesz uchwycić wyjątek, który może wywołać połączenie.

  • Łagodź powolność refleksji:

W najnowszych wersjach .Net Framework CreateDelegate pokonał 50-krotnie metodę 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);

drawPołączenia będzie około 50x szybciej niż MethodInfo.Invoke wykorzystania drawjako standard Functak:

var res = draw(methodParams);

Sprawdź mój post, aby zobaczyć wyniki testów wywoływania różnych metod

Fab
źródło
1
Chociaż dostaję zastrzyk zależności powinien być preferowanym sposobem testowania jednostek, ale uważam, że używanie go ostrożnie nie jest całkowicie złe, gdy używa się refleksji, aby uzyskać dostęp do jednostek, które w innym przypadku byłyby niedostępne do testowania. Osobiście uważam, że oprócz zwykłych [publicznych] [chronionych] [prywatnych] modyfikatorów powinniśmy także mieć modyfikatory [Test] [Kompozycja], aby można było mieć pewne rzeczy widoczne na tych etapach bez konieczności upublicznienia wszystkiego ( i dlatego muszą w pełni udokumentować te metody)
andrew pate
1
Dzięki Fab za wykazanie problemów z refleksją nad prywatnymi członkami, spowodowałem, że sprawdziłem, jak się czuję przy korzystaniu z niego i doszedłem do wniosku ... Używanie refleksji do testowania jednostkowego twoich prywatnych członków jest błędne, jednak pozostawienie nietestowanych ścieżek kodu jest naprawdę bardzo źle.
andrew pate
2
Ale jeśli chodzi o testowanie jednostkowe kodu starszego typu, którego nie da się przetestować, jest to świetny sposób
TS
2

Czy nie możesz mieć innej metody rysowania dla każdego typu, który chcesz narysować? Następnie wywołaj przeciążoną metodę Draw przekazującą obiekt typu itemType, który ma zostać narysowany.

Twoje pytanie nie wyjaśnia, czy itemType rzeczywiście odnosi się do obiektów różnych typów.

Peter Hession
źródło
1

Myślę, że możesz przekazać to BindingFlags.NonPublictam , gdzie jest GetMethodmetoda.

Armin Ronacher
źródło
1

Wywołuje dowolną metodę pomimo poziomu ochrony w instancji obiektu. Cieszyć się!

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
źródło
0

Przeczytaj tę (dodatkową) odpowiedź (która czasami jest odpowiedzią), aby zrozumieć, dokąd to zmierza i dlaczego niektórzy ludzie w tym wątku narzekają, że „wciąż nie działa”

Napisałem dokładnie ten sam kod, co jedną z odpowiedzi tutaj . Ale wciąż miałem problem. Położyłem punkt przerwania

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

Wykonało się, ale mi == null

I tak się zachowywało, dopóki nie „przebudowałem” wszystkich zaangażowanych projektów. Testowałem jedno urządzenie, gdy metoda odbicia znajdowała się w trzecim. To było całkowicie mylące, ale użyłem Immediate Window, aby odkryć metody i odkryłem, że prywatna metoda, którą próbowałem przetestować, miała starą nazwę (zmieniłem jej nazwę). To powiedziało mi, że stary zestaw lub PDB wciąż tam jest, nawet jeśli buduje się projekt testu jednostkowego - z jakiegoś powodu projekt, którego testy nie zbudował. „przebudowa” działała

TS
źródło