2012-02-29 13 views
6

Ciao Sto cercando di creare una funzione che crea dinamicamente un delegato con lo stesso valore di ritorno e gli stessi parametri di un MethodInfo che riceve come parametro e anche e questo è molto importante gli stessi nomi dei parametri !Creazione delegati dinamicamente con nomi parametro

Quello che ho fatto finora è creare una funzione che restituisce un lambda che riceve gli stessi tipi di parametri e ha lo stesso valore di ritorno come il MethodInfo, ma non ha i nomi dei parametri:

static void Example() 
    { 
     Person adam = new Person(); 
     MethodInfo method = typeof(Person).GetMethod("Jump"); 
     Delegate result = CreateDelegate(adam, method); 
     result.DynamicInvoke((uint)4, "Yeahaa"); 
    } 

    private static Delegate CreateDelegate(object instance, MethodInfo method) 
    { 
     var parametersInfo = method.GetParameters(); 
     Expression[] expArgs = new Expression[parametersInfo.Length]; 
     List<ParameterExpression> lstParamExpressions = new List<ParameterExpression>(); 
     for (int i = 0; i < expArgs.Length; i++) 
     { 
      expArgs[i] = Expression.Parameter(parametersInfo[i].ParameterType, parametersInfo[i].Name); 
      lstParamExpressions.Add((ParameterExpression)expArgs[i]); 
     } 

     MethodCallExpression callExpression = Expression.Call(Expression.Constant(instance), method, expArgs); 
     LambdaExpression lambdaExpression = Expression.Lambda(callExpression, lstParamExpressions); 

     return lambdaExpression.Compile(); 
    } 

    private class Person 
    { 
     public void Jump(uint height, string cheer) 
     { 
      Console.WriteLine("Person jumped " + height + " "+ cheer); 
     } 
    } 

fa Qualcuno ha qualche suggerimento su come posso farlo? Per chiarire, il motivo per cui mi interessa dei nomi dei parametri è che sarei in grado di attivare il delegato con i nomi dei parametri, quindi potrei chiamarlo così (cheer = "YAY!", Height = 3) (La mia applicazione è integrata con Python è così che potrò farlo senza DynamicInvoke e questo è anche il motivo per cui i nomi dei parametri sono così importanti e anche perché ho scritto '=' e non ':')

risposta

6

Per creare dinamicamente un delegato, puoi utilizzare Reflection.Emit. Poiché i delegati sono tipi speciali in .Net, il codice per crearlo non è del tutto ovvio.Il seguente è basato sul codice riflesso dei metodi usati da Expression.Lambda(). Lì, è usato per creare tipi di delegati personalizzati in situazioni in cui non sono disponibili delegati Action o Func (mo re di 17 parametri o parametri con ref o out).

class DelegateTypeFactory 
{ 
    private readonly ModuleBuilder m_module; 

    public DelegateTypeFactory() 
    { 
     var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
      new AssemblyName("DelegateTypeFactory"), AssemblyBuilderAccess.RunAndCollect); 
     m_module = assembly.DefineDynamicModule("DelegateTypeFactory"); 
    } 

    public Type CreateDelegateType(MethodInfo method) 
    { 
     string nameBase = string.Format("{0}{1}", method.DeclaringType.Name, method.Name); 
     string name = GetUniqueName(nameBase); 

     var typeBuilder = m_module.DefineType(
      name, TypeAttributes.Sealed | TypeAttributes.Public, typeof(MulticastDelegate)); 

     var constructor = typeBuilder.DefineConstructor(
      MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public, 
      CallingConventions.Standard, new[] { typeof(object), typeof(IntPtr) }); 
     constructor.SetImplementationFlags(MethodImplAttributes.CodeTypeMask); 

     var parameters = method.GetParameters(); 

     var invokeMethod = typeBuilder.DefineMethod(
      "Invoke", MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Public, 
      method.ReturnType, parameters.Select(p => p.ParameterType).ToArray()); 
     invokeMethod.SetImplementationFlags(MethodImplAttributes.CodeTypeMask); 

     for (int i = 0; i < parameters.Length; i++) 
     { 
      var parameter = parameters[i]; 
      invokeMethod.DefineParameter(i + 1, ParameterAttributes.None, parameter.Name); 
     } 

     return typeBuilder.CreateType(); 
    } 

    private string GetUniqueName(string nameBase) 
    { 
     int number = 2; 
     string name = nameBase; 
     while (m_module.GetType(name) != null) 
      name = nameBase + number++; 
     return name; 
    } 
} 

Se vi preoccupate per le prestazioni, si potrebbe desiderare di creare una cache di qualche tipo, in modo che non si crea il tipo più e più volte lo stesso delegato.

L'unica modifica nel codice sarà la linea che crea lambdaExpression:

LambdaExpression lambdaExpression = Expression.Lambda(
    s_delegateTypeFactory.CreateDelegateType(method), 
    callExpression, lstParamExpressions); 

Ma in realtà non c'è bisogno di trattare con Expression s affatto. Delegate.CreateDelegate() è sufficiente:

private static Delegate CreateDelegate(object instance, MethodInfo method) 
{ 
    return Delegate.CreateDelegate(
     s_delegateTypeFactory.CreateDelegateType(method), instance, method); 
} 
+0

Grazie I l'ho provato prima oggi e ha funzionato benissimo !! Capisco la maggior parte del tuo codice, ma non riesco davvero a capire cosa stai facendo con il nome e il nomeBase, perché è uguale al nome del tipo di base + il nome del tipo, qual è il numero = 2 per? Comunque grazie mille stavo cercando di farlo funzionare per giorni –

+0

@UchihaMadara, è solo un modo per assicurarsi che il nome del tipo sia unico, perché non è possibile avere due tipi con lo stesso nome nello stesso assembly. E il numero 2 è lì in modo che i nomi siano come 'PersonJump',' PersonJump2', 'PersonJump3', ecc. – svick

+0

Vedo che è molto intelligente, grazie! =] –

0

Il framework open source ImpromptuInterface (v5.6.7 tramite NuGet) ha un DLR currying/partial applicano implementazione che penso avrebbe funzionato in questo caso fino a quando non hai bisogno di un delegato letterale.

ecco la versione C# di creare e invocandolo:

dynamic jump =Impromptu.Curry(adam).Jump(); 
jump(cheer:"yay", height:(uint)3); 

Quindi jump non è un delegato letterale, non si può riflettere, ma è possibile richiamare direttamente come se si trattasse di un delegato e è un oggetto DLR quindi la mia ipotesi è che funzionerebbe lo stesso in Python.

0

Ho appena inciampato su un bel modo per risolvere questo problema, sembra che questo per i delegati ad un metodo statico:

private static Delegate CreateDelegate(MethodInfo method) { 
    var paramTypes = method.GetParameters().Select(p => p.ParameterType); 

    Type delegateType = Expression.GetDelegateType(paramTypes.Append(method.ReturnType).ToArray()); 

    return Delegate.CreateDelegate(delegateType, method, true); 
} 

Esso utilizza questo metodo di estensione:

public static IEnumerable<TSource> Append<TSource>(this IEnumerable<TSource> collection, TSource element) { 
    if (collection == null) throw new ArgumentNullException("collection"); 

    foreach (TSource element1 in collection) yield return element1; 
    yield return element; 
} 
Problemi correlati