Erstellen Sie Func oder Action für jede Methode (mit Reflektion in c #)

c# expression-trees lambda reflection

Frage

Meine Anwendung arbeitet mit dem dynamischen Laden von DLLs, basierend auf Einstellungen aus der Datenbank (Datei-, Klassen- und Methodennamen). Um den Einsatz von Reflektion zu erleichtern, zu beschleunigen und zu reduzieren, hätte ich gerne einen Cache ....

Nach der Idee, dass:

 MethodInfo.Invoke

Ist nichts performativ ( Reflection Performance - Create Delegate (Eigenschaften C #) ) Ich möchte jeden Aufruf in Methoden übersetzen. Ich dachte an etwas, das so funktionieren würde:

public static T Create<T>(Type type, string methodName) // or
public static T Create<T>(MethodInfo info) // to use like this:
var action = Create<Action<object>>(typeof(Foo), "AnySetValue");

Eine Voraussetzung ist, dass alle Parameter Objekt sein können.

Ich versuche mit Ausdrücken umzugehen, und bisher habe ich so etwas:

    private void Sample()
    {
        var assembly = Assembly.GetAssembly(typeof(Foo));

        Type customType = assembly.GetType("Foo");

        var actionMethodInfo = customType.GetMethod("AnyMethod");
        var funcMethodInfo = customType.GetMethod("AnyGetString");
        var otherActionMethod = customType.GetMethod("AnySetValue");
        var otherFuncMethodInfo = customType.GetMethod("OtherGetString");

        var foo = Activator.CreateInstance(customType);
        var actionAccessor = (Action<object>)BuildSimpleAction(actionMethodInfo);
        actionAccessor(foo);

        var otherAction = (Action<object, object>)BuildOtherAction(otherActionMethod);
        otherAction(foo, string.Empty);

        var otherFuncAccessor = (Func<object, object>)BuildFuncAccessor(funcMethodInfo);
        otherFuncAccessor(foo);

        var funcAccessor = (Func<object,object,object>)BuildOtherFuncAccessor(otherFuncMethodInfo);
        funcAccessor(foo, string.Empty);
    }

    static Action<object> BuildSimpleAction(MethodInfo method)
    {
        var obj = Expression.Parameter(typeof(object), "o");

        Expression<Action<object>> expr =
            Expression.Lambda<Action<object>>(
                Expression.Call(
                    Expression.Convert(obj, method.DeclaringType),
                    method), obj);

        return expr.Compile();
    }

    static Func<object, object> BuildFuncAccessor(MethodInfo method)
    {
        var obj = Expression.Parameter(typeof(object), "o");

        Expression<Func<object, object>> expr =
            Expression.Lambda<Func<object, object>>(
                Expression.Convert(
                    Expression.Call(
                        Expression.Convert(obj, method.DeclaringType),
                        method),
                    typeof(object)),
                obj);

        return expr.Compile();

    }

    static Func<object, object, object> BuildOtherFuncAccessor(MethodInfo method)
    {
        var obj = Expression.Parameter(typeof(object), "o");
        var value = Expression.Parameter(typeof(object));

        Expression<Func<object, object, object>> expr =
            Expression.Lambda<Func<object, object, object>>(
                    Expression.Call(
                        Expression.Convert(obj, method.DeclaringType),
                        method,
                        Expression.Convert(value, method.GetParameters()[0].ParameterType)), 
                        obj, value);

        return expr.Compile();

    }

    static Action<object, object> BuildOtherAction(MethodInfo method)
    {
        var obj = Expression.Parameter(typeof(object), "o");
        var value = Expression.Parameter(typeof(object));

        Expression<Action<object, object>> expr =
            Expression.Lambda<Action<object, object>>(
                Expression.Call(
                    Expression.Convert(obj, method.DeclaringType),
                    method,
                    Expression.Convert(value, method.GetParameters()[0].ParameterType)),
                obj,
                value);

        return expr.Compile();
    }

public class Foo
{
    public void AnyMethod() {}

    public void AnySetValue(string value) {}

    public string AnyGetString()
    {            return string.Empty;        }

    public string OtherGetString(string value)
    {            return string.Empty;        }
}

Gibt es eine Möglichkeit, diesen Code zu vereinfachen? (Ich glaube, es ist möglich, eine Methode nur mit generischen zu erstellen.) Und wenn Sie 3, 4, 5, irgendwelche Parameter wie ich haben?


Ich dachte, was wäre, wenn es so etwas gäbe:

https://codereview.stackexchange.com/questions/1070/generic-advanced-delegate-createdelegate-using-expression-trees

aber ich habe mehr einen Parameter (in Aktion oder Funktion), diesen Parameter (ersten Parameter) ein Objekt zu erfüllen. Ist das möglich?

Akzeptierte Antwort

Ich habe ein Beispielprogramm erstellt, das alle Ihre Anforderungen erfüllt (denke ich!)

class Program
{
    class MyType
    {
        public MyType(int i) { this.Value = i; }

        public void SetValue(int i) { this.Value = i; }

        public void SetSumValue(int a, int b) { this.Value = a + b; }

        public int Value { get; set; }
    }

    public static void Main()
    {
        Type type = typeof(MyType);

        var mi = type.GetMethod("SetValue");

        var obj1 = new MyType(1);
        var obj2 = new MyType(2);

        var action = DelegateBuilder.BuildDelegate<Action<object, int>>(mi);

        action(obj1, 3);
        action(obj2, 4);

        Console.WriteLine(obj1.Value);
        Console.WriteLine(obj2.Value);

        // Sample passing a default value for the 2nd param of SetSumValue.
        var mi2 = type.GetMethod("SetSumValue");

        var action2 = DelegateBuilder.BuildDelegate<Action<object, int>>(mi2, 10);

        action2(obj1, 3);
        action2(obj2, 4);

        Console.WriteLine(obj1.Value);
        Console.WriteLine(obj2.Value);

        // Sample without passing a default value for the 2nd param of SetSumValue.
        // It will just use the default int value that is 0.
        var action3 = DelegateBuilder.BuildDelegate<Action<object, int>>(mi2);

        action3(obj1, 3);
        action3(obj2, 4);

        Console.WriteLine(obj1.Value);
        Console.WriteLine(obj2.Value);
    }
}

DelegateBuilder-Klasse:

public class DelegateBuilder
{
    public static T BuildDelegate<T>(MethodInfo method, params object[] missingParamValues)
    {
        var queueMissingParams = new Queue<object>(missingParamValues);

        var dgtMi = typeof(T).GetMethod("Invoke");
        var dgtRet = dgtMi.ReturnType;
        var dgtParams = dgtMi.GetParameters();

        var paramsOfDelegate = dgtParams
            .Select(tp => Expression.Parameter(tp.ParameterType, tp.Name))
            .ToArray();

        var methodParams = method.GetParameters();

        if (method.IsStatic)
        {
            var paramsToPass = methodParams
                .Select((p, i) => CreateParam(paramsOfDelegate, i, p, queueMissingParams))
                .ToArray();

            var expr = Expression.Lambda<T>(
                Expression.Call(method, paramsToPass),
                paramsOfDelegate);

            return expr.Compile();
        }
        else
        {
            var paramThis = Expression.Convert(paramsOfDelegate[0], method.DeclaringType);

            var paramsToPass = methodParams
                .Select((p, i) => CreateParam(paramsOfDelegate, i + 1, p, queueMissingParams))
                .ToArray();

            var expr = Expression.Lambda<T>(
                Expression.Call(paramThis, method, paramsToPass),
                paramsOfDelegate);

            return expr.Compile();
        }
    }

    private static Expression CreateParam(ParameterExpression[] paramsOfDelegate, int i, ParameterInfo callParamType, Queue<object> queueMissingParams)
    {
        if (i < paramsOfDelegate.Length)
            return Expression.Convert(paramsOfDelegate[i], callParamType.ParameterType);

        if (queueMissingParams.Count > 0)
            return Expression.Constant(queueMissingParams.Dequeue());

        if (callParamType.ParameterType.IsValueType)
            return Expression.Constant(Activator.CreateInstance(callParamType.ParameterType));

        return Expression.Constant(null);
    }
}

Wie es funktioniert

Der Kern ist die BuildDelegate- Methode:

static T BuildDelegate<T>(MethodInfo method)

  • T ist der Delegattyp, den Sie erstellen möchten.
  • method ist die MethodInfo der Methode, die vom generierten Delegaten aufgerufen werden soll.

Beispielaufruf: var action = BuildDelegate<Action<object, int>>(mi);

Regeln für Parameter:

  • Wenn die übergebene Methode eine Instanzmethode ist, akzeptiert der erste Parameter des generierten Delegates die Instanz des Objekts, das die Methode selbst enthält. Alle anderen Parameter werden an die Methode übergeben.

  • Wenn die übergebene Methode eine statische Methode ist, werden alle Parameter des generierten Delegaten an die Methode übergeben.

  • Bei fehlenden Parametern werden Standardwerte übergeben.

  • Zusätzliche Parameter werden verworfen.

Beliebte Antwort

Delegate.CreateDelegate ist viel einfacher als das Erstellen von Ausdrucksbäumen.

    var assembly = Assembly.GetAssembly(typeof(Foo));

    Type customType = assembly.GetType("Foo");

    var actionMethodInfo = customType.GetMethod("AnyMethod");

    var foo = Activator.CreateInstance(customType);

    Action action = (Action)Delegate.CreateDelegate(typeof(Action), foo, actionMethodInfo);


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