How to serialize method call expression with arguments?

c# expression-trees json reflection

Question

I have a call to a remote service which is described as following:

var user = new User { Name = "check" };
WcfService<IMyService>.Call(s => s.MyMethod(1, "param", user, new Entity { ID = 2 }));

In my Call method, I need to serialize this method call to JSON, which will be put in the WebSphere queue:

{
    "Interface": "IMyService",
    "Method": "MyMethod",
    "Arguments": [
        1,
        "param",
        {
            "Name": "check"
        },
        {
            "ID": 2
        }
    ]
}

I know how to obtain interface and method names, but I cannot obtain non-constant values:

public static class WcfService<TInterface>
{
    public static void Call(Expression<Action<TInterface>> expr)
    {
        var mce = (MethodCallExpression)expr.Body;

        string interfaceName = typeof(TInterface).Name;
        string methodName = mce.Method.Name;

        var args = mce.Arguments
            .Cast<ConstantExpression>()
            .Select(e => e.Value)
            .ToArray();
    }
}

This code works for 1 and "param", but does not work for user and new Entity { ID = 2 }) since they are FieldExpression and NewExpression respectively. How to get the actual values, passed to a function call, instead of their expression representation?

Update: The answer from the suggested duplicate question is not suitable, because I don't want to compile my expression and execute it - I only need to evaluate arguments.

1
0
5/23/2017 12:14:50 PM

Accepted Answer

I have combined the information from this answer and Pieter Witvoet's answer and received the following function:

public static object Evaluate(Expression expr)
{
    switch (expr.NodeType)
    {
        case ExpressionType.Constant:
            return ((ConstantExpression)expr).Value;
        case ExpressionType.MemberAccess:
            var me = (MemberExpression)expr;
            object target = Evaluate(me.Expression);
            switch (me.Member.MemberType)
            {
                case MemberTypes.Field:
                    return ((FieldInfo)me.Member).GetValue(target);
                case MemberTypes.Property:
                    return ((PropertyInfo)me.Member).GetValue(target, null);
                default:
                    throw new NotSupportedException(me.Member.MemberType.ToString());
            }
        case ExpressionType.New:
            return ((NewExpression)expr).Constructor
                .Invoke(((NewExpression)expr).Arguments.Select(Evaluate).ToArray());
        default:
            throw new NotSupportedException(expr.NodeType.ToString());
    }
}

Now, I can simply do the following:

var args = mce.Arguments.Select(ExpressionEvaluator.Evaluate).ToArray();
2
5/23/2017 12:33:06 PM

Popular Answer

A FieldExpression has a Member (the field) and an Expression, in this case a ConstantExpression that holds the object that contains that field. Here the Value of that constant expression is an anonymous object that captures the local user variable. Casting Member to FieldInfo allows you to call GetValue on it, and by passing in the Value of that constant expression you'll get the User object you're looking for.

A NewExpression has a Constructor and an Arguments property (a list of expressions), but there is no value, because a new object is only produced when you actually invoke that function. You could compile that expression, invoke it and serialize the result, or you could serialize the constructor call itself - similar to how you are serializing that service call.

Note that { "Name": "check" } and { "ID": 2 } are missing type information.



Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow