Delegaten dynamisch mit Parameternamen erstellen

c# delegates dynamic expression-trees parameters

Frage

Hi Ich versuche, eine Funktion zu erstellen, die dynamisch einen Delegaten mit dem gleichen Rückgabewert und den gleichen Parametern wie eine MethodInfo erstellt, die es als Parameter empfängt und auch und das ist sehr wichtig die gleichen Parameternamen!

Was ich bis jetzt gemacht habe, ist eine Funktion zu erstellen, die ein Lambda zurückgibt, das die gleichen Parametertypen empfängt und denselben Rückgabewert wie MethodInfo hat, aber keine Parameternamen hat:

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

Hat jemand irgendwelche Vorschläge, wie ich das machen kann? Um es klar zu machen, ist der Grund, warum ich mich um die Parameternamen sorge, so dass ich den Delegaten mit den Parameternamen aktivieren könnte, also könnte ich es so nennen (cheer = "YAY! ', Height = 3) (Meine Anwendung ist mit Python integriert, so kann ich das ohne DynamicInvoke machen und das ist auch der Grund, warum die Parameternamen so wichtig sind und warum ich auch '=' und nicht 'geschrieben habe:')

Akzeptierte Antwort

Um einen Delegaten dynamisch zu erstellen, können Sie Reflection.Emit verwenden. Da Delegaten spezielle Typen in .NET sind, ist der Code zum Erstellen nicht eindeutig. Das Folgende basiert auf dem reflektierten Code der von Expression.Lambda() verwendeten Methoden. Dort wird es verwendet, um benutzerdefinierte Func in Situationen zu erstellen, in denen kein Action oder Func Delegat verfügbar ist (mehr als 17 Parameter oder Parameter mit ref oder 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;
    }
}

Wenn Sie Wert auf Leistung legen, möchten Sie möglicherweise einen Cache einiger Art erstellen, damit Sie nicht immer denselben Delegattyp erstellen.

Die einzige Änderung in Ihrem Code ist die Zeile, die lambdaExpression erstellt:

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

Aber Sie brauchen sich mit Expression überhaupt nicht zu beschäftigen. Delegate.CreateDelegate() ist genug:

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

Beliebte Antwort

Ich bin gerade auf einen netten Weg gestolpert, um dieses Problem zu lösen. Für Delegierte einer statischen Methode sieht das so aus:

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

Es verwendet diese Erweiterungsmethode:

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


Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow