Search code examples
c#expression-trees

Wrapping an Expression Tree with a Logger


I'm doing some work in Expression Trees. When you call ToString() on an Expression Tree, you get a lovely bit of diagnostic text (here is a sampling):

 ((Param_0.Customer.LastName == "Doe") 
     AndAlso ((Param_0.Customer.FirstName == "John") 
     Or (Param_0.Customer.FirstName == "Jane")))

So I wrote this bit of code, in an attempt to wrap the Expression with some logging capability:

public Expression WithLog(Expression exp)
{
    return Expression.Block(exp, Expression.Call(
        typeof (Debug).GetMethod("Print",
            new Type [] { typeof(string) }),
        new [] { exp } ));
}

I half-expected the method call to infer the ToString() usage, but I suppose that's a compile-time feature. When I execute this, I get the error:

Expression of type 'System.Boolean' cannot be used for parameter of type 'System.String' of method 'Void Print(System.String)

Fair enough. But when I change it to this:

public Expression WithLog(Expression exp)
{
    return Expression.Block(exp, Expression.Call(
        typeof (Debug).GetMethod("Print",
            new Type [] { typeof(string) }),
        new [] { exp.ToString() } ));
}

It doesn't compile. Why? And what do I need to do to fix this?


Solution

  • As per my comment, it's expecting Expression[], but you've passed it string[]. You can do this, which will immediately run ToString() on exp:

    public Expression WithLog(Expression exp)
    {
        return Expression.Block(Expression.Call(
            typeof (Debug).GetMethod("Print",
                new Type [] { typeof(string) }),
            new [] { Expression.Constant(exp.ToString()) } ), exp);
    }
    

    Which yields:

    Print("c => ((c.LastName == "Doe") AndAlso ((c.FirstName == "John") OrElse (c.LastName == "Jane")))")
    

    Alternatively, you could change Expression.Constant(exp.ToString()) to be an invocation of ToString on exp, so that the ToString executed when you invoke the expression.

    public Expression WithLog(Expression exp)
    {
        return Expression.Block(Expression.Call(
            typeof (Debug).GetMethod("Print",
                new Type [] { typeof(string) }),
            new [] { Expression.Call(Expression.Constant(exp), exp.GetType().GetMethod("ToString")) } ), exp);
    }
    

    Which gives:

    Print(c => ((c.LastName == "Doe") AndAlso ((c.FirstName == "John") OrElse (c.LastName == "Jane"))).ToString())