2012-02-29 8 views
6

Hola, estoy tratando de crear una función que crea dinámicamente un delegado con el mismo valor de retorno y los mismos parámetros que MethodInfo recibe como parámetro y también y es muy importante el mismo nombre de parámetro !Crear delegados dinámicamente con los nombres de los parámetros

Lo que hice hasta ahora es crear una función que devuelve un lambda que recibe los mismos tipos de parámetros y tiene el mismo valor de retorno como el MethodInfo pero no tiene los nombres de los parámetros:

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

¿El Alguien tiene alguna sugerencia de cómo puedo hacer eso? Para dejar en claro, el motivo por el que me importan los nombres de los parámetros es para poder activar el delegado con los nombres de los parámetros, así podría llamarlo así (cheer = "YAY! ', Height = 3) (Mi aplicación está integrada con Python, así es como podré hacerlo sin DynamicInvoke y esta es también la razón por la cual los nombres de los parámetros son tan importantes y también por qué escribí '=' y no ':')

Respuesta

6

Para crear dinámicamente un delegado, puede usar Reflection.Emit. Dado que los delegados son tipos especiales en .Net, el código para crearlo no es del todo obvio. Lo siguiente se basa en el código reflejado de los métodos utilizados por Expression.Lambda(). Allí, se usa para crear tipos de delegados personalizados en situaciones donde no hay ningún delegado Action o Func disponible (mo re de 17 parámetros o parámetros 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; 
    } 
} 

Si se preocupan por el rendimiento, es posible que desee crear una caché de algún tipo, por lo que no se crea escriba una y otra vez el mismo delegado.

La única modificación en el código será la línea que crea lambdaExpression:

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

Pero en realidad no tienen que hacer frente a Expression s en absoluto. Delegate.CreateDelegate() es suficiente:

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

Gracias I lo intenté temprano hoy y funcionó muy bien !! Entiendo la mayor parte de tu código, pero realmente no pude entender qué es lo que estás haciendo con el nombre y la Base de nombres, ¿por qué es igual al nombre de la base tipo + el nombre del tipo, para qué sirve el número = 2? De todos modos muchas gracias, estaba tratando de hacerlo funcionar por días –

+0

@UchihaMadara, es solo una manera de asegurarse de que el nombre del tipo sea único, porque no puede tener dos tipos con el mismo nombre en el mismo ensamblaje. Y el número 2 está ahí para que los nombres sean como 'PersonJump',' PersonJump2', 'PersonJump3', etc. – svick

+0

Veo que es muy inteligente, ¡gracias! =] –

0

El framework de código abierto ImpromptuInterface (v5.6.7 través Nuget) tiene un DLR currying/partial aplican aplicación que creo que funcionaría en este caso, siempre y cuando no es necesario un delegado literal.

Aquí está la versión C# de crearlo e invocando que:

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

Así jump no es un delegado literal, no se puede reflejar, pero se puede invocar directamente como si se tratara de un delegado y es un objeto DLR, así que supongo que funcionaría igual en Python.

0

acabo tropezado con una buena manera de resolver este problema, parece que esto para los delegados a un método estático:

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

Se utiliza este método de extensión:

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