Search code examples
c#linqexpression-trees

Get the parameter value from a Linq Expression


I have the following class

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

Now I have a helper class that looks like this

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 = ???
    }
}

The idea ist to use this code from somewhere:

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

The point where I am stuck is how to extract the arguments from the expression itself.

I found this way

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

which seems to be an object type with a field "product" but I believe there must be a simpler solution.

Any suggestions.

Update

Just to clarify, I modified my code according to what I want to achive. In my real word application I already have a class that does the same but without an expression tree:

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

The main reason for my Helper<T> is to have Compile-Time checking if the method signature is valid.

Update 2

This is my current implementation, is there a better way to acces the values, without using reflection?

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

Solution

  • This method works pretty well. It returns the argument types and values for an 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();
            }
        }