Creazione di delegati in modo dinamico con nomi di parametri

c# delegates dynamic expression-trees parameters

Domanda

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 con gli stessi nomi dei parametri!

Quello che ho fatto finora è creare una funzione che restituisca un lambda che riceve gli stessi tipi di parametro e ha lo stesso valore di ritorno di 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);
        }
    }

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 è integrato 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 accettata

Per creare dinamicamente un delegato, puoi utilizzare Reflection.Emit. Poiché i delegati sono tipi speciali in .Net, il codice per crearlo non è abbastanza ovvio. Quanto segue si basa sul codice riflesso dei metodi utilizzati da Expression.Lambda() . In questo caso, viene utilizzato per creare tipi di delegati personalizzati in situazioni in cui non sono disponibili delegati Action o Func (più 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 ti interessano le prestazioni, potresti voler creare una cache di qualche tipo, in modo da non creare lo stesso tipo di delegato più e più volte.

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

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

Ma in realtà non è necessario occuparsi affatto di Expression . Delegate.CreateDelegate() è sufficiente:

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

Risposta popolare

Mi sono appena imbattuto in un buon modo per risolvere questo problema, sembra questo per i delegati a 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);
}

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


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché