Search code examples
c#lambdaexpressioncode-reusefunc

Is there a way to capture a lambda expression so that it's not compile-time forced to take on an identity as either an Expression or Delegate type?


Suppose I have a complex lambda expression as follows:

x => x.A.HasValue || (x.B.HasValue && x.C == q) || (!x.C.HasValue && !x.A.HasValue) || //...expression goes on

I want to use this as an Expression<Func<T,bool> in (e.g. Linq-To-Entities) Queryable.Where method. I also want to use it in the Enumerable.Where method, but the Where method only accepts a Func<T,bool>, not an Expression<Func<T,bool>.

The lambda syntax itself can be used to generate either an Expression<Func<T,bool>> or a Func<T,bool> (or any delegate type for that matter), but in this context it cannot generate more than one at once.

For example, I can write:

public Expression<Func<Pair,bool>> PairMatchesExpression()
{
    return x => x.A == x.B;
}

as easily as I can write:

public Func<Pair,bool> PairMatchesDelegate()
{
    return x => x.A == x.B;
}

The problem is that I cannot use the same exact lambda expression (i.e. x => x.A == x.B) in both ways, without physically duplicating it into two separate methods with two different return types, in spite of the compiler's ability to compile it into either one.

In other words, if I'd like to use the lambda expression in the Queryable methods, then I have to use the Expression method signature. Once I do that however, I cannot use it as a Func as easily as I could have had I just declared the method return type as Func. Instead, I now have to call Compile on the Expression and then worry about caching the results manually like so:

static Func<Pair,bool> _cachedFunc;
public Func<Pair,bool> PairMatchesFunc()
{
    if (_cachedFunc == null)
        _cachedFunc = PairMatchesExpression().Compile();
    return _cachedFunc;
}

Is there a solution to this problem so that I can use the lambda expression in a more general way without it being locked down to a particular type at compile-time?


Solution

  • Unfortunately, I can see no way to truly get, at compile time, a Func and an Expression from the same lambda. However, you could at least encapsulate away the difference, and you can also defer the compilation of the Func until the first time it's used. Here's a solution that makes the best of things and may meet your needs, even though it doesn't quite go all the way to what you really wanted (compile-time evaluation of both the Expression and the Func).

    Please note that this works fine without using the [DelegateConstraint] attribute (from Fody.ExtraConstraints), but with it, you will get compile-time checking of the constructor parameter. The attributes make the classes act like they have a constraint where T : Delegate, which is not currently supported in C#, even though it is supported in the ILE (not sure if I'm saying that right, but you get the idea).

    public class VersatileLambda<[DelegateConstraint] T> where T : class {
        private readonly Expression<T> _expression;
        private readonly Lazy<T> _funcLazy;
    
        public VersatileLambda(Expression<T> expression) {
            if (expression == null) {
                throw new ArgumentNullException(nameof(expression));
            }
            _expression = expression;
            _funcLazy = new Lazy<T>(expression.Compile);
        }
    
        public static implicit operator Expression<T>(VersatileLambda<T> lambda) {
            return lambda?._expression;
        }
    
        public static implicit operator T(VersatileLambda<T> lambda) {
            return lambda?._funcLazy.Value;
        }
    
        public Expression<T> AsExpression() { return this; }
        public T AsLambda() { return this; }
    }
    
    public class WhereConstraint<[DelegateConstraint] T> : VersatileLambda<Func<T, bool>> {
        public WhereConstraint(Expression<Func<T, bool>> lambda)
            : base(lambda) { }
    }
    

    The beauty of the implicit conversion is that in contexts where a specific Expression<Func<>> or Func<> is expected, you don't have to do anything at all, just, use it.

    Now, given an object:

    public partial class MyObject {
        public int Value { get; set; }
    }
    

    That is represented in the database like so:

    CREATE TABLE dbo.MyObjects (
        Value int NOT NULL CONSTRAINT PK_MyObjects PRIMARY KEY CLUSTERED
    );
    

    Then it works like this:

    var greaterThan5 = new WhereConstraint<MyObject>(o => o.Value > 5);
    
    // Linq to Objects
    List<MyObject> list = GetObjectsList();
    var filteredList = list.Where(greaterThan5).ToList(); // no special handling
    
    // Linq to Entities
    IQueryable<MyObject> myObjects = new MyObjectsContext().MyObjects;
    var filteredList2 = myObjects.Where(greaterThan5).ToList(); // no special handling
    

    If implicit conversion isn't suitable, you can cast explicitly to the target type:

    var expression = (Expression<Func<MyObject, bool>>) greaterThan5;
    

    Note that you don't really need the WhereConstraint class, or you could get rid of VersatileLambda by moving its contents to WhereConstraint, but I liked making the two separate (as now you can use VersatileLambda for something that returns other than a bool). (And this difference is largely what sets apart my answer from Diego's.) Using VersatileLambda as it is now looks like this (you can see why I wrapped it):

    var vl = new VersatileLambda<Func<MyObject, bool>>(o => o.Value > 5);
    

    I have confirmed that this works perfectly for IEnumerable as well as IQueryable, properly projecting the lambda expression into the SQL, as proven by running SQL Profiler.

    Also, you can do some really cool things with expressions that can't be done with lambdas. Check this out:

    public static class ExpressionHelper {
        public static Expression<Func<TFrom, TTo>> Chain<TFrom, TMiddle, TTo>(
            this Expression<Func<TFrom, TMiddle>> first,
            Expression<Func<TMiddle, TTo>> second
        ) {
            return Expression.Lambda<Func<TFrom, TTo>>(
               new SwapVisitor(second.Parameters[0], first.Body).Visit(second.Body),
               first.Parameters
            );
        }
    
        // this method thanks to Marc Gravell   
        private class SwapVisitor : ExpressionVisitor {
            private readonly Expression _from;
            private readonly Expression _to;
    
            public SwapVisitor(Expression from, Expression to) {
                _from = from;
                _to = to;
            }
    
            public override Expression Visit(Expression node) {
                return node == _from ? _to : base.Visit(node);
            }
        }
    }
    
    var valueSelector = new Expression<Func<MyTable, int>>(o => o.Value);
    var intSelector = new Expression<Func<int, bool>>(x => x > 5);
    var selector = valueSelector.Chain<MyTable, int, bool>(intSelector);
    

    You can create an overload of Chain that takes a VersatileLambda as the first parameter, and returns a VersatileLambda. Now you're really sizzling along.