Come posso usare la riflessione per invocare un metodo privato?


326

Esistono un gruppo di metodi privati ​​nella mia classe e devo chiamarne uno dinamicamente basato su un valore di input. Sia il codice invocante che i metodi target sono nella stessa istanza. Il codice è simile al seguente:

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

In questo caso, GetMethod()non restituirà metodi privati. Cosa BindingFlagsdevo fornire per GetMethod()poter individuare metodi privati?

Risposte:


498

Basta cambiare il codice per utilizzare la versioneGetMethod sovraccarica di BindingFlags che accetta:

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

Ecco la documentazione di enumerazione BindingFlags .


248
Mi metterò così tanto nei guai con questo.
Frank Schwieterman,

1
BindingFlags.NonPublicprivatemetodo di non ritorno .. :(
Moumit

4
@MoumitMondal i tuoi metodi sono statici? Devi specificare BindingFlags.Instancesia BindingFlags.NonPublicper i metodi non statici.
BrianS,

No @BrianS .. il metodo è non-statice privatee la classe è ereditata da System.Web.UI.Page.. mi rende comunque sciocco .. non ho trovato il motivo .. :(
Moumit

3
L'aggiunta BindingFlags.FlattenHierarchyti consentirà di ottenere metodi dalle classi principali alla tua istanza.
Dragon Thoughts

67

BindingFlags.NonPublicnon restituirà alcun risultato da solo. A quanto pare, combinarlo con BindingFlags.Instancefa il trucco.

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

La stessa logica si applica anche alle internalfunzioni
supertopi

Ho un problema simile. Cosa succede se "this" è una classe figlio e si tenta di invocare il metodo privato del genitore?
persianLife,

È possibile usarlo per invocare metodi protetti della classe base.base?
Shiv

51

E se vuoi davvero metterti nei guai, rendi più semplice l'esecuzione scrivendo un metodo di estensione:

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

E utilizzo:

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

14
Pericoloso? Sì. Ma una grande estensione di supporto se racchiusa nel mio spazio dei nomi di testing dell'unità. Grazie per questo.
Robert Wahler,

5
Se ti interessano le eccezioni reali generate dal metodo chiamato, è una buona idea avvolgerlo in try catch block e rilanciare invece l'eccezione interna quando TargetInvokationException viene catturato. Lo faccio nella mia estensione dell'helper del test unitario.
Slobodan Savkovic,

2
Riflessione pericolosa? Hmmm ... C #, Java, Python ... in realtà tutto è pericoloso, anche il mondo: D Devi solo prenderti cura di come farlo in sicurezza ...
Legends

16

Microsoft ha recentemente modificato l'API di riflessione rendendo obsolete la maggior parte di queste risposte. Di seguito dovrebbero funzionare su piattaforme moderne (inclusi Xamarin.Forms e UWP):

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

O come metodo di estensione:

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

Nota:

  • Se il metodo desiderato è in una superclasse objdel Tgenerico deve essere esplicitamente impostato al tipo della superclasse.

  • Se il metodo è asincrono è possibile utilizzare await (Task) obj.InvokeMethod(…).


Non funziona per la versione UWP .net almeno perché funziona solo con metodi pubblici : " Restituisce una raccolta che contiene tutti i metodi pubblici dichiarati sul tipo corrente che corrispondono al nome specificato ".
Dmytro Bondarenko,

1
@DmytroBondarenko L'ho provato con metodi privati ​​e ha funzionato. L'ho visto, comunque. Non sono sicuro del perché si comporti diversamente dalla documentazione, ma almeno funziona.
Owen James,

Sì, non chiamerei tutte le altre risposte obsolete, se la documentazione dice che GetDeclareMethod()è destinato a essere utilizzato per recuperare solo un metodo pubblico.
Mass Dot Net

10

Sei assolutamente sicuro che questo non possa essere fatto attraverso l'eredità? La riflessione è l'ultima cosa che dovresti considerare quando risolvi un problema, rende più difficile il refactoring, la comprensione del codice e qualsiasi analisi automatizzata.

Sembra che dovresti avere solo una classe DrawItem1, DrawItem2, ecc. Che sovrascrive il tuo dynMethod.


1
@Bill K: date altre circostanze, abbiamo deciso di non usare l'ereditarietà per questo, quindi l'uso della riflessione. Nella maggior parte dei casi, lo faremmo in questo modo.
Jeromy Irvine,

8

La riflessione, in particolare sui membri privati, è sbagliata

  • La riflessione rompe il tipo di sicurezza. Puoi provare a invocare un metodo che non esiste (più), o con parametri sbagliati, o con troppi parametri, o non abbastanza ... o anche nell'ordine sbagliato (questo è il mio preferito :)). A proposito, anche il tipo di ritorno potrebbe cambiare.
  • La riflessione è lenta.

La riflessione dei membri privati ​​infrange il principio di incapsulamento e quindi espone il codice a quanto segue:

  • Aumenta la complessità del tuo codice perché deve gestire il comportamento interno delle classi. Ciò che è nascosto dovrebbe rimanere nascosto.
  • Semplifica la rottura del codice poiché verrà compilato ma non verrà eseguito se il metodo ha cambiato nome.
  • Rende il codice privato facile da violare perché se è privato non è destinato a essere chiamato in quel modo. Forse il metodo privato prevede uno stato interiore prima di essere chiamato.

E se dovessi farlo comunque?

Ci sono così casi, quando dipendi da una terza parte o hai bisogno di alcune API non esposte, devi fare qualche riflessione. Alcuni lo usano anche per testare alcune classi che possiedono ma che non vogliono cambiare l'interfaccia per dare accesso ai membri interni solo per i test.

Se lo fai, fallo bene

  • Mitigare il facile da rompere:

Per mitigare il problema di una facile interruzione, la cosa migliore è individuare eventuali interruzioni testando unità test che verrebbero eseguite in una build di integrazione continua o simili. Ovviamente, significa che usi sempre lo stesso assembly (che contiene i membri privati). Se usi un carico dinamico e una riflessione, ti piace giocare con il fuoco, ma puoi sempre cogliere l'eccezione che la chiamata può produrre.

  • Mitigare la lentezza della riflessione:

Nelle versioni recenti di .Net Framework, CreateDelegate ha battuto un fattore 50 invocato da 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);

drawle chiamate sono intorno a 50x più veloce di MethodInfo.Invoke utilizzare drawcome standard Funcdel genere:

var res = draw(methodParams);

Controlla questo mio post per vedere il benchmark su invocazioni di metodi diversi


1
Anche se ricevo l'iniezione di dipendenza dovrebbe essere un modo preferito di test unitari, immagino che usare con cautela non sia del tutto malvagio usare la riflessione per accedere a unità che altrimenti sarebbero inaccessibili per i test. Personalmente penso che, oltre ai normali modificatori [pubblici] [protetti] [privati], dovremmo anche avere modificatori [Test] [Composizione], quindi è possibile avere certe cose visibili durante queste fasi senza essere costretti a rendere tutto completamente pubblico ( e quindi devono documentare completamente quei metodi)
andate pate

1
Grazie a Fab per aver elencato i problemi di riflessione sui membri privati, mi ha spinto a rivedere come mi sento di usarlo e sono giunto a una conclusione ... L'uso della riflessione per testare l'unità dei tuoi membri privati ​​è sbagliato, tuttavia lasciare percorsi di codice non testati è davvero molto sbagliato.
Andrew Pate,

2
Ma quando si tratta di test unitari di codice legacy generalmente non unit-testable, è un modo geniale per farlo
TS

2

Non potresti semplicemente avere un metodo Draw diverso per ogni tipo che vuoi disegnare? Quindi chiama il metodo Draw sovraccaricato passando l'oggetto di tipo itemType da disegnare.

La tua domanda non chiarisce se itemType si riferisce realmente a oggetti di tipi diversi.


1

Penso che si può passare BindingFlags.NonPublicin cui essa è il GetMethodmetodo.


1

Richiama qualsiasi metodo nonostante il suo livello di protezione sull'istanza dell'oggetto. Godere!

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

0

Leggi questa risposta (supplementare) (che a volte è la risposta) per capire dove sta andando e perché alcune persone in questo thread si lamentano che "non funziona ancora"

Ho scritto esattamente lo stesso codice di una delle risposte qui . Ma ho ancora avuto un problema. Ho inserito il punto di interruzione

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

Ha eseguito ma mi == null

E ha continuato a comportarsi così fino a quando non ho "ricostruito" tutti i progetti coinvolti. Stavo testando un'unità di un assieme mentre il metodo di riflessione era in terza assemblea. Era totalmente confuso, ma ho usato Finestra immediata per scoprire i metodi e ho scoperto che un metodo privato che ho provato a testare l'unità aveva un vecchio nome (l'ho rinominato). Questo mi ha detto che il vecchio assembly o PDB è ancora disponibile anche se il progetto di unit test viene compilato - per qualche motivo il progetto non è stato realizzato. "ricostruire" ha funzionato


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.