2013-05-03 11 views
32

Ho bisogno di un metodo che prende un'istanza MethodInfo che rappresenta un metodo statico non generico con firma arbitraria e restituisce un delegato associato a quel metodo che potrebbe essere successivamente richiamato utilizzando il metodo Delegate.DynamicInvoke. Il mio primo tentativo ingenuo si presentava così:Come creare un delegato da un MethodInfo quando la firma del metodo non può essere conosciuta in anticipo?

using System; 
using System.Reflection; 

class Program 
{ 
    static void Main() 
    { 
     var method = CreateDelegate(typeof (Console).GetMethod("WriteLine", new[] {typeof (string)})); 
     method.DynamicInvoke("Hello world"); 
    } 

    static Delegate CreateDelegate(MethodInfo method) 
    { 
     if (method == null) 
     { 
      throw new ArgumentNullException("method"); 
     } 

     if (!method.IsStatic) 
     { 
      throw new ArgumentNullException("method", "The provided method is not static."); 
     } 

     if (method.ContainsGenericParameters) 
     { 
      throw new ArgumentException("The provided method contains unassigned generic type parameters."); 
     } 

     return method.CreateDelegate(typeof(Delegate)); // This does not work: System.ArgumentException: Type must derive from Delegate. 
    } 
} 

ho sperato che il metodo MethodInfo.CreateDelegate riusciva a capire il tipo delegato corretto se stessa. Beh, ovviamente non può. Quindi, come posso creare un'istanza di System.Type che rappresenta un delegato con una firma corrispondente all'istanza MethodInfo fornita?

+1

Perché si vuole creare un delegato e utilizzare DynamicInvoke? L'uso di DynamicInvoke è molto più lento di MethodInfo.Invoke. –

+0

@nawfal Nope. Un duplicato richiede che la domanda posta qui possa essere risolta nella domanda che hai citato. Il richiedente vuole poter usare 'MethodInfo.CreateDelegate()' quando il tipo che rappresenta la firma del metodo non è noto. Nell'altra domanda, questo è già noto come 'MyDelegate', e quindi non è utile al problema di questo asker. – einsteinsci

+0

Chi diavolo sta cancellando i miei commenti? Non è la prima volta! Scusa @ einsteinsci Non riesco a trovare quale thread ho postato qui come duplicato, quindi non posso controllare. Se potessi pubblicare apprezzerò. – nawfal

risposta

29

È possibile utilizzare System.Linq.Expressions.Expression.GetDelegateType metodo:

using System; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Reflection; 

class Program 
{ 
    static void Main() 
    { 
     var writeLine = CreateDelegate(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) })); 
     writeLine.DynamicInvoke("Hello world"); 

     var readLine = CreateDelegate(typeof(Console).GetMethod("ReadLine", Type.EmptyTypes)); 
     writeLine.DynamicInvoke(readLine.DynamicInvoke()); 
    } 

    static Delegate CreateDelegate(MethodInfo method) 
    { 
     if (method == null) 
     { 
      throw new ArgumentNullException("method"); 
     } 

     if (!method.IsStatic) 
     { 
      throw new ArgumentException("The provided method must be static.", "method"); 
     } 

     if (method.IsGenericMethod) 
     { 
      throw new ArgumentException("The provided method must not be generic.", "method"); 
     } 

     return method.CreateDelegate(Expression.GetDelegateType(
      (from parameter in method.GetParameters() select parameter.ParameterType) 
      .Concat(new[] { method.ReturnType }) 
      .ToArray())); 
    } 
} 

v'è probabilmente un errore di copia-incolla nel 2 ° controllo per !method.IsStatic - non si deve usare ArgumentNullException lì. Ed è un buon stile fornire un nome parametro come argomento a ArgumentException.

Utilizzare method.IsGenericMethod se si desidera rifiutare tutti i metodi generici e method.ContainsGenericParameters se si desidera rifiutare solo i metodi generici con parametri di tipo non sostituiti.

3

Si consiglia di provare System.Linq.Expressions

... 
using System.Linq.Expressions; 
... 

static Delegate CreateMethod(MethodInfo method) 
{ 
    if (method == null) 
    { 
     throw new ArgumentNullException("method"); 
    } 

    if (!method.IsStatic) 
    { 
     throw new ArgumentException("The provided method must be static.", "method"); 
    } 

    if (method.IsGenericMethod) 
    { 
     throw new ArgumentException("The provided method must not be generic.", "method"); 
    } 

    var parameters = method.GetParameters() 
          .Select(p => Expression.Parameter(p.ParameterType, p.Name)) 
          .ToArray(); 
    var call = Expression.Call(null, method, parameters); 
    return Expression.Lambda(call, parameters).Compile(); 
} 

e utilizzarlo in seguito come segue

var method = CreateMethod(typeof (Console).GetMethod("WriteLine", new[] {typeof (string)})); 
method.DynamicInvoke("Test Test"); 
+3

Questa soluzione presenta un sovraccarico significativo: costruisce un albero di espressioni, esegue un compilatore di albero di espressioni, genera un metodo dinamico e crea un delegato per tale metodo. Quindi, tutte le chiamate successive al delegato passano attraverso questo metodo dinamico proxy non necessario. È molto meglio creare un delegato direttamente associato all'istanza 'MethodInfo' fornita. –

+0

@OksanaGimmel L'intero processo è fatto solo per ottenere il delegato. Una volta che hai il riferimento del delegato, è solo questione di invocarlo. – nawfal

+0

@nwafal, Anche se è vero che questo viene eseguito in modo ottimale come inizializzazione di una sola volta per host CLR o incarnazione di AppDomain, ciò non toglie nulla al commento di Oksana, considerando che il richiedente non ha indicato quante volte il delegato sarà successivamente richiamato, rispetto a quanti delegati distinti di questo tipo richiede una determinata sessione. E nota che, anche nel migliore dei casi di single-init/multiple-use, se questo è l'unico uso di 'Linq.Expressions' nell'app, stai prendendo un colpo significativo per risolvere e caricare le librerie in eccesso, e probabilmente nel momento peggiore, durante il boot-up. –

Problemi correlati