Search code examples
c#.netlambdaexpression-trees

force Expression<> to evaluate local variables


I have something like this in LinqPad

void Main()
{
    var t1 = DateTimeOffset.Parse("10/1/2012");

    int? n1 = 1;

    Expression<Func<Sample,bool>> x1 = ud => 
        (ud.Date == t1 && ud.Number == n1);

    x1.ToString().Dump();
}

class Sample
{
    public int? Number{set;get;}
    public DateTimeOffset Date{set;get;}
}

it outputs

ud => ((ud.Date == value(UserQuery+<>c_DisplayClass0).t1) AndAlso (ud.Number == value(UserQuery+<>c_DisplayClass0).n1))

is there any possible way to keep the variables but have it output something like this:

ud => ((ud.Date == Parse("10/1/2012")) AndAlso (ud.Number == Convert(1)))


Solution

  • Here we go; output first:

    ud => ((ud.Date == 10/01/2012 00:00:00 +00:00) AndAlso (ud.Number == 1))
    

    This will never output Parse(...), because your expression does not contain a parse: you have already evaluated that by the time you put it into a lambda.

    Note also that this handles one level of captured variable. For more complex (nested) capture contexts, you'll have to recursively fetch the values from the capture classes:

    using System;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    static class Program
    {
        static void Main()
        {
            var t1 = DateTimeOffset.Parse("10/1/2012");
    
            int? n1 = 1;
    
            Expression<Func<Sample, bool>> x1 = ud =>
                (ud.Date == t1 && ud.Number == n1);
    
            var sanitized = (Expression<Func<Sample, bool>>)
                new Literalizer().Visit(x1);
    
            Console.WriteLine(sanitized.ToString());
        }
    }
    
    class Literalizer : ExpressionVisitor
    {
        protected override Expression VisitMember(MemberExpression node)
        {
            if(node.Member.DeclaringType.IsDefined(typeof(CompilerGeneratedAttribute), false)
                && node.Expression.NodeType == ExpressionType.Constant)
            {
                object target = ((ConstantExpression)node.Expression).Value, value;
                switch (node.Member.MemberType)
                {
                    case MemberTypes.Property:
                        value = ((PropertyInfo)node.Member).GetValue(target, null);
                        break;
                    case MemberTypes.Field:
                        value = ((FieldInfo)node.Member).GetValue(target);
                        break;
                    default:
                        value = target = null;
                        break;
                }
                if (target != null) return Expression.Constant(value, node.Type);
            }
            return base.VisitMember(node);
        }
    }
    
    class Sample
    {
        public int? Number{set;get;}
        public DateTimeOffset Date{set;get;}
    }