[c#] Come si usa la riflessione per richiamare un metodo privato?


Answers

BindingFlags.NonPublic non restituirà alcun risultato da solo. Come si scopre, combinandolo con BindingFlags.Instance fa il trucco.

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

Ci sono un gruppo di metodi privati ​​nella mia classe, e ho bisogno di chiamarne uno in modo dinamico sulla base di un valore di input. Sia il codice di richiamo che i metodi di destinazione si trovano nella stessa istanza. Il codice si presenta così:

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

In questo caso, GetMethod() non restituirà metodi privati. Quali BindingFlags devo fornire a GetMethod() modo che possa individuare metodi privati?




Sei assolutamente sicuro che ciò non possa essere fatto attraverso l'ereditarietà? Reflection è l'ultima cosa da considerare quando si risolve 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 sovrascriva il dynMethod.




Penso che puoi passarlo BindingFlags.NonPublic dove è il metodo GetMethod .




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



La riflessione specialmente sui membri privati ​​è sbagliata

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

La riflessione dei membri privati ​​rompe il principio di encapsulation e quindi espone il tuo codice al seguente:

  • Aumenta la complessità del tuo codice perché deve gestire il comportamento interno delle classi. Ciò che è nascosto dovrebbe rimanere nascosto.
  • Rende il tuo codice facile da interrompere in quanto verrà compilato ma non verrà eseguito se il metodo ha cambiato il suo nome.
  • Rende il codice privato facile da rompere perché se è privato non è destinato a essere chiamato in quel modo. Forse il metodo privato si aspetta 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 qualche api non esposta, 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 facile da interrompere, il migliore è rilevare qualsiasi potenziale interruzione testando in unit test che verrebbero eseguiti in una build di integrazione continua o simile. Certo, significa che usi sempre lo stesso assembly (che contiene i membri privati). Se usi un carico e una riflessione dinamici, ti piace giocare con il fuoco, ma puoi sempre catturare l'Eccezione che la chiamata potrebbe produrre.

  • Mitiga la lentezza della riflessione:

Nelle recenti versioni di .Net Framework, CreateDelegate ha battuto per un fattore 50 il metodoInfo:

// 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 chiamate sarà circa 50 volte più veloce di MethodInfo.Invoke usa draw come Func standard in questo modo:

var res = draw(methodParams);

Controlla questo post per vedere il benchmark su diverse chiamate di metodi




Related