2013-05-03 10 views
32

Potrzebuję metody, która pobiera instancję MethodInfo reprezentującą nietypową metodę statyczną z dowolną sygnaturą i zwraca delegata powiązanego z tą metodą, która może później zostać wywołana przy użyciu metody Delegate.DynamicInvoke. Moja pierwsza naiwna próba wyglądała tak:Jak utworzyć delegata z MethodInfo, gdy podpis metody nie może być wcześniej znany?

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

Mam nadzieję, że metoda MethodInfo.CreateDelegate mógł dowiedzieć się prawidłową sam typ delegata. Cóż, oczywiście nie może. Jak utworzyć instancję System.Type reprezentującą delegata z sygnaturą zgodną z podaną instancją MethodInfo?

+1

Dlaczego chcesz stworzyć delegata i użyć DynamicInvoke? Korzystanie z DynamicInvoke jest dużo wolniejsze niż MethodInfo.Invoke. –

+0

@Nawfal Nope. Kopia wymaga, aby na pytanie zadane tutaj można było odpowiedzieć w zadanym przez Ciebie pytaniu. Pytający chce móc użyć 'MethodInfo.CreateDelegate()', gdy typ reprezentujący sygnaturę metody nie jest znany. W drugim pytaniu jest to już znane jako 'MyDelegate', a zatem nie jest pomocne dla problemu tego pytającego. – einsteinsci

+0

Kto do cholery usuwa moje komentarze? Nie pierwszy raz! Sorry @einsteinsci Nie mogę znaleźć wątku, który opublikowałem jako duplikat, więc nie mogę go sprawdzić. Jeśli mógłbyś opublikować, docenię. – nawfal

Odpowiedz

29

Można użyć System.Linq.Expressions.Expression.GetDelegateType metody:

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

Jest to prawdopodobnie błąd copy-paste w 2. czeku na !method.IsStatic - nie należy używać ArgumentNullException tam. Dobrym stylem jest podanie nazwy parametru jako argumentu dla ArgumentException.

Użyj method.IsGenericMethod, jeśli chcesz odrzucić wszystkie ogólne metody i method.ContainsGenericParameters, jeśli chcesz odrzucić tylko ogólne metody z niepopartymi parametrami typu.

3

Możesz spróbować 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(); 
} 

i używać go później jako następujące

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

Rozwiązanie to ma znaczny narzut: tworzy drzewo wyrażeń, uruchamia kompilator drzewa wyrażeń, generuje metodę dynamiczną i tworzy delegata do tej metody. Następnie wszystkie kolejne wywołania do uczestnika przechodzą przez tę niepotrzebną metodę dynamiczną proxy. Znacznie lepiej jest utworzyć delegata bezpośrednio związanego z podaną instancją "MethodInfo". –

+0

@OksanaGimmel Cały proces odbywa się tylko po to, aby uzyskać delegata. Kiedy już masz referencje delegatów, jest to tylko kwestia wywołania go. – nawfal

+0

@nwafal, Chociaż prawdą jest, że jest to optymalnie wykonane jako jednorazowa inicjalizacja dla każdego hosta CLR lub wcielenia AppDomain, nie pomniejsza to komentarza Oksany, ponieważ osoba pytająca nie wskazała, ile razy delegat zostanie następnie wywołany, a ilu delegatów tego typu wymaga dana sesja. Zauważ, że nawet w najlepszym przypadku single-init/multiple-use, jeśli jest to jedyne użycie 'Linq.Expressions' w aplikacji, otrzymujesz znaczące trafienie, aby rozwiązać i załadować nadmiar bibliotek i prawdopodobnie w najgorszym momencie podczas rozruchu. –

Powiązane problemy