Search code examples
c#jsonreflectionexpression-trees

How to serialize method call expression with arguments?


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.


Solution

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