Appelez une MethodCallExpression avec un nombre dynamique de paramètres

c# expression-trees

Question

J'ai besoin d'appeler le suivant:

public MethodCallExpression CreateLazyMethod(object instance, MethodBase foundMethodInfo)
{
    var orderedParameters = MethodParametersEvaluator.OrderParameters(foundMethodInfo.GetParameters());
    var paramExpressions = orderedParameters.Select(x => (Expression)Expression.Parameter(x.GetType())).ToArray();
    return Expression.Call((MethodInfo)foundMethodInfo, paramExpressions);
}

et résolvez les paramètres au moment de l'exécution

var callabel = CreateLazyMethod(instance, foundMethodInfo);
var runtimeEvaluatedParamsList=new object[]{1,654,};
callable.Invoke(runtimeEvaluatedParamsList);

Des solutions?

Réponse acceptée

Vous pouvez l'envoyer en tant object[] , puis utiliser un élément spécifique lors de la génération de la méthode.

Fondamentalement pour "Test Long String".Substring(5); Le code ci-dessous générera cette méthode:

object DynamicMethod(object[] params)
{
    return "Test Long String".Substring((int)params[0]);
}

L’inconvénient est que result et params sont des objets, ce qui produira des opérations de boxing \ unboxing inutiles, mais si vous connaissez la signature, vous pouvez écrire une implémentation générique utilisant des types spécifiques.

Exemple de code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

public class Program
{
    public static void Main(string[] args)
    {
        // test non static method with result
        string test = "Test String";
        MethodInfo method = typeof(string).GetMethod("Substring", BindingFlags.Public | BindingFlags.Instance, null, new Type[]{typeof(int)},null);
        Func<object[], object> lazyMethod = CreateLazyMethodWithResult(test, method);
        object result = lazyMethod(new object[] { 5 });
        Console.WriteLine(result);
        Console.WriteLine();

        // test static method with no result
        var staticMethod = typeof(Program).GetMethod("StaticMethod", BindingFlags.Static | BindingFlags.Public);
        var staticAction = CreateLazyStaticMethodWithNoResult(staticMethod);

        staticAction(new object[]{"Test message"});
        Console.WriteLine();

        //test static method with result
        var staticMethodWithResult = typeof(Program).GetMethod("StaticMethodWithResult", BindingFlags.Static | BindingFlags.Public);
        var staticActionWithResult = CreateLazyStaticMethodWithResult(staticMethodWithResult);

        Console.WriteLine(staticActionWithResult(new object[] { "Test message" }));
        Console.WriteLine();

        // sample with constructor
        var constructorCall = typeof(TestClass).GetConstructors().First();
        var constructorAction = GenerateLazyConstructorCall(constructorCall);

        var createdObject = constructorAction(new object[] { "Test message" });

        Console.WriteLine("Created type is " + createdObject.GetType().FullName);
    }

    //Test class
    public class TestClass
    {
        public TestClass(string message)
        {
            Console.WriteLine("----Constructor is called with message - " + message);
        }
    }

    public static void StaticMethod(string message)
    {
        Console.WriteLine("----Static method is called with " + message);
    }

    public static string StaticMethodWithResult(string message)
    {
        Console.WriteLine("----Static method with result is called with " + message);
        return "Hello from static method";
    }

    public static Func<object[], object> CreateLazyMethodWithResult(object instance, MethodInfo method)
    {
        ParameterExpression allParameters;
        var methodCall = GenerateCallExpression(instance, method, out allParameters);
        var lambda = Expression.Lambda<Func<object[], object>>(methodCall, allParameters);
        return lambda.Compile();
    }

    public static Action<object[]> CreateLazyMethodWithNoResult(object instance, MethodInfo method)
    {
        ParameterExpression allParameters;
        var methodCall = GenerateCallExpression(instance, method, out allParameters);
        var lambda = Expression.Lambda<Action<object[]>>(methodCall, allParameters);
        return lambda.Compile();
    }

    public static Func<object[], object> CreateLazyStaticMethodWithResult(MethodInfo method)
    {
        ParameterExpression allParameters;
        var methodCall = GenerateCallExpression(null, method, out allParameters);
        var lambda = Expression.Lambda<Func<object[], object>>(methodCall, allParameters);
        return lambda.Compile();
    }

    public static Action<object[]> CreateLazyStaticMethodWithNoResult(MethodInfo method)
    {
        ParameterExpression allParameters;
        var methodCall = GenerateCallExpression(null, method, out allParameters);
        var lambda = Expression.Lambda<Action<object[]>>(methodCall, allParameters);
        return lambda.Compile();
    }


    /// <summary>
    /// Generate expression call
    /// </summary>
    /// <param name="instance">If instance is NULL, then it method will be treated as static method</param>
    private static MethodCallExpression GenerateCallExpression(object instance, MethodBase method, out ParameterExpression allParameters)
    {
        var parameters = GenerateParameters(method, out allParameters);

        var methodInfo = method as MethodInfo;
        // it's non static method
        if (instance != null)
        {
            var instanceExpr = Expression.Convert(Expression.Constant(instance), instance.GetType());
            return Expression.Call(instanceExpr, methodInfo, parameters.ToArray());
        }

        // it's static method
        return Expression.Call(methodInfo, parameters.ToArray());
    }

    public static Func<object[], object> GenerateLazyConstructorCall(ConstructorInfo constructor)
    {
        ParameterExpression allParameters;
        var parameters = GenerateParameters(constructor, out allParameters);

        var newExpr = Expression.New(constructor, parameters.ToArray());
        var lambda = Expression.Lambda<Func<object[], object>>(newExpr, allParameters);

        return lambda.Compile();
    }

    private static List<Expression> GenerateParameters(MethodBase method, out ParameterExpression allParameters)
    {
        allParameters = Expression.Parameter(typeof(object[]), "params");
        ParameterInfo[] methodMarameters = method.GetParameters();
        List<Expression> parameters = new List<Expression>();
        for (int i = 0; i < methodMarameters.Length; i++)
        {
            var indexExpr = Expression.Constant(i);
            var item = Expression.ArrayIndex(allParameters, indexExpr);
            var converted = Expression.Convert(item, methodMarameters[i].ParameterType);
            parameters.Add(converted);
        }

        return parameters;
    }
}

Exemple de travail réel: https://dotnetfiddle.net/lHB2kE

UPDATE J'ai mis à jour le code avec des exemples pour les méthodes d'appel et le constructeur:

  1. Méthode d'instance sans objet de retour
  2. Méthode d'instance avec un objet de retour
  3. Méthode statique sans objet de retour
  4. Méthode statique avec un objet de retour
  5. Exemple d'utilisation du constructeur


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