Création dynamique de délégués avec des noms de paramètres

c# delegates dynamic expression-trees parameters

Question

Bonjour, j'essaie de créer une fonction qui crée dynamiquement un délégué avec la même valeur de retour et les mêmes paramètres qu'un MethodInfo qu'il reçoit en tant que paramètre. De plus, il est très important que les mêmes noms de paramètres!

Ce que j’ai fait jusqu’à présent, c’est de créer une fonction qui retourne un lambda qui reçoit les mêmes types de paramètre et a la même valeur de retour que MethodInfo mais qui n’a pas le nom du paramètre:

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

Quelqu'un a-t-il des suggestions pour que je puisse faire cela? Pour que ce soit clair, la raison pour laquelle je me soucie des noms de paramètre est que je pourrais activer le délégué avec les noms de paramètre afin que je puisse l'appeler ainsi (cheer = "YAY! ', Height = 3) (Mon application est intégré à Python, c’est ainsi que je pourrai le faire sans DynamicInvoke et c’est aussi la raison pour laquelle les noms de paramètres sont si importants et aussi pourquoi j’ai écrit '=' et non ':')

Réponse acceptée

Pour créer dynamiquement un délégué, vous pouvez utiliser Reflection.Emit. Comme les délégués sont des types spéciaux en .Net, le code pour le créer n’est pas tout à fait évident. Ce qui suit est basé sur le code reflété des méthodes utilisées par Expression.Lambda() . Là, il est utilisé pour créer des types de délégués personnalisés dans les situations où aucun délégué Action ou Func n'est disponible (plus de 17 paramètres ou paramètres avec ref ou 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 vous vous souciez des performances, vous pouvez créer un cache de sorte à ne pas créer le même type de délégué encore et encore.

La seule modification dans votre code sera la ligne qui crée lambdaExpression :

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

Mais vous n’avez vraiment pas besoin de vous occuper d’ Expression . Delegate.CreateDelegate() est suffisant:

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

Réponse populaire

Je viens de tomber sur un bon moyen de résoudre ce problème, il ressemble à ceci pour les délégués à une méthode statique:

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

Il utilise cette méthode d'extension:

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


Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow