Obtenir la valeur du paramètre à partir d'une expression Linq

c# expression-trees linq

Question

J'ai la classe suivante

public class MyClass
{
    public bool Delete(Product product)
    {
        // some code.
    }
}

Maintenant, j'ai une classe d'assistance qui ressemble à ceci

public class Helper<T, TResult>
{

    public Type Type;
    public string Method;
    public Type[] ArgTypes;
    public object[] ArgValues;

    public Helper(Expression<Func<T, TResult>> expression)
    {
        var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;

        this.Type = typeof(T);
        this.Method = body.Method.Name;
        this.ArgTypes = body.Arguments.Select(x => x.Type).ToArray();
        this.ArgValues = ???
    }
}

L'idée est d'utiliser ce code quelque part:

// I am returning a helper somewhere
public Helper<T> GetMethod<T>()
{
    var product = GetProduct(1);
    return new Helper<MyClass>(x => x.Delete(product));
}

// some other class decides, when to execute the helper 
// Invoker already exists and is responsible for executing the method
// that is the main reason I don't just comile and execute my Expression
public bool ExecuteMethod<T>(Helper<T> helper)
{
    var instance = new MyClass();
    var Invoker = new Invoker(helper.Type, helper.Method, helper.ArgTypes, helper.ArgValues);
    return (bool)Invoker.Invoke(instance);
}

Le point sur lequel je suis coincé est de savoir comment extraire les arguments de l'expression elle-même.

J'ai trouvé ça

((ConstantExpression)((MemberExpression)body.Arguments[0]).Expression).Value

qui semble être un type d'objet avec un champ "produit" mais je crois qu'il doit y avoir une solution plus simple.

Aucune suggestion.

Mettre à jour

Juste pour clarifier, j'ai modifié mon code en fonction de ce que je veux accomplir. Dans ma vraie application, j'ai déjà une classe qui fait la même chose mais sans arbre d'expression:

var helper = new Helper(typeof(MyClass), "Delete", 
    new Type[] { typeof(Product) }, new object[] {product}));

La raison principale de mon Helper<T> est de permettre à Compile-Time de vérifier si la signature de la méthode est valide.

Mise à jour 2

C’est ma mise en œuvre actuelle. Existe-t-il un meilleur moyen d’accéder aux valeurs sans utiliser la réflexion?

public Helper(Expression<Func<T, TResult>> expression)
{
    var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;

    this.Type = typeof(T);
    this.Method = body.Method.Name;
    this.ArgTypes = body.Arguments.Select(x => x.Type).ToArray();

    var values = new List<object>();
    foreach(var arg in body.Arguments)
    {
        values.Add(
            (((ConstantExpression)exp.Expression).Value).GetType()
                .GetField(exp.Member.Name)
                .GetValue(((ConstantExpression)exp.Expression).Value);
        );
    }
    this.ArgValues = values.ToArray();
}

Réponse acceptée

Cette méthode fonctionne plutôt bien. Il retourne les types d'arguments et les valeurs pour une expression>

    private static KeyValuePair<Type, object>[] ResolveArgs<T>(Expression<Func<T, object>> expression)
    {
        var body = (System.Linq.Expressions.MethodCallExpression)expression.Body;
        var values = new List<KeyValuePair<Type, object>>();

        foreach (var argument in body.Arguments)
        {
            var exp = ResolveMemberExpression(argument);
            var type = argument.Type;

            var value = GetValue(exp);

            values.Add(new KeyValuePair<Type, object>(type, value));
        }

        return values.ToArray();
    }

    public static MemberExpression ResolveMemberExpression(Expression expression)
    {

        if (expression is MemberExpression)
        {
            return (MemberExpression)expression;
        }
        else if (expression is UnaryExpression)
        {
            // if casting is involved, Expression is not x => x.FieldName but x => Convert(x.Fieldname)
            return (MemberExpression)((UnaryExpression)expression).Operand;
        }
        else
        {
            throw new NotSupportedException(expression.ToString());
        }
    }

    private static object GetValue(MemberExpression exp)
    {
        // expression is ConstantExpression or FieldExpression
        if (exp.Expression is ConstantExpression)
        {
            return (((ConstantExpression)exp.Expression).Value)
                    .GetType()
                    .GetField(exp.Member.Name)
                    .GetValue(((ConstantExpression)exp.Expression).Value);    
        }
        else if (exp.Expression is MemberExpression)
        {
            return GetValue((MemberExpression)exp.Expression);
        }
        else
        {
            throw new NotImplementedException();
        }
    }

Réponse populaire

Vous pouvez compiler l'expression d'argument puis l'invoquer pour calculer la valeur:

var values = new List<object>();
foreach(var arg in body.Arguments)
{
    var value = Expression.Lambda(argument).Compile().DynamicInvoke();
    values.Add(value);
}
this.ArgValues = values.ToArray();


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