Creando delegados dinámicamente con nombres de parámetros.

c# delegates dynamic expression-trees parameters

Pregunta

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 un MethodInfo que recibe como parámetro y también, ¡y esto es muy importante con los mismos nombres de parámetros!

Lo que hice hasta ahora es crear una función que devuelva un lambda que reciba los mismos tipos de parámetros y tenga el mismo valor de retorno que 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);
        }
    }

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

Respuesta aceptada

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 utiliza para crear tipos de delegado costumbre en situaciones donde no hay Action o Func delegado disponible (más 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 le importa el rendimiento, es posible que desee crear un caché de algún tipo, para que no cree el mismo tipo de delegado una y otra vez.

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

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

Pero en realidad no necesitas lidiar con 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);
}

Respuesta popular

Acabo de encontrar una buena manera de resolver este problema, se ve así 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);
}

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


Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow