有没有办法捕获lambda表达式,以便它不是编译时强制将身份作为表达式或委托类型?

假设我有一个复杂的lambda表达式,如下所示:

x => xAHasValue || (xBHasValue && xC == q) || (!xCHasValue && !xAHasValue) || //...expression goes on 

我想将它用作Expression<Func in(例如Linq-To-Entities) Queryable.Where方法。 我也想在Enumerable.Where方法中使用它,但Where方法只接受Func ,而不是Expression<Func

lambda语法本身可用于生成Expression<Func>Func (或任何委托类型),但在此上下文中,它不能一次生成多个。

例如,我可以写:

 public Expression<Func> PairMatchesExpression() { return x => xA == xB; } 

就像我写的那样容易:

 public Func PairMatchesDelegate() { return x => xA == xB; } 

问题是我无法以两种方式使用相同的lambda表达式(即x => xA == xB),而无需将其物理地复制到具有两种不同返回类型的两个单独方法中,尽管编译器能够将其编译为任意一个。

换句话说,如果我想在Queryable方法中使用lambda表达式,那么我必须使用Expression方法签名。 然而,一旦我这样做, 我不能像我刚才那样轻松地将它用作Func ,我只是将方法返回类型声明为Func 。 相反,我现在必须在Expression上调用Compile ,然后担心手动缓存结果,如下所示:

 static Func _cachedFunc; public Func PairMatchesFunc() { if (_cachedFunc == null) _cachedFunc = PairMatchesExpression().Compile(); return _cachedFunc; } 

是否有解决此问题的方法,以便我可以以更一般的方式使用lambda表达式,而不会在编译时将其锁定到特定类型?

不幸的是,我无法在编译时从同一个lambda中获得FuncExpression 。 但是,您至少可以封装差异,也可以将Func的编译推迟到第一次使用时。 这是一个充分利用并满足您需求的解决方案,即使它并不能完全满足您的需求(对ExpressionFunc编译时评估)。

请注意,这可以在使用[DelegateConstraint]属性(来自Fody.ExtraConstraints )的情况下正常工作,但是使用它,您将获得构造函数参数的编译时检查。 这些属性使得这些类的行为就像它们有一个约束where T : Delegate在C#中目前不受支持,即使它在ILE中得到支持(不确定我是否说得对,但你明白了)。

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

隐式转换的优点在于,在期望特定Expression>Func<>上下文中,您不必执行任何操作,只需使用它。

现在,给出一个对象:

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

这在数据库中表示如下:

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

然后它的工作原理如下:

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

如果隐式转换不合适,则可以显式转换为目标类型:

 var expression = (Expression>) greaterThan5; 

请注意,您并不真正需要WhereConstraint类,或者您可以通过将其内容移动到WhereConstraint来摆脱VersatileLambda ,但我喜欢将两者分开(现在您可以使用VersatileLambda来返回除bool之外的其他内容)。 (而这种差异在很大程度上解决了我对Diego的回答。)使用VersatileLambda现在看起来像这样(你可以看到为什么我把它包起来):

 var vl = new VersatileLambda>(o => o.Value > 5); 

我已经确认这对IEnumerable以及IQueryable ,正确地将lambda表达式投影到SQL中,正如运行SQL Profiler所certificate的那样。

此外,你可以使用lambdas无法完成的表达式做一些非常酷的事情。 看一下这个:

 public static class ExpressionHelper { public static Expression> Chain( this Expression> first, Expression> second ) { return Expression.Lambda>( 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>(o => o.Value); var intSelector = new Expression>(x => x > 5); var selector = valueSelector.Chain(intSelector); 

您可以创建一个Chain的重载,它将VersatileLambda作为第一个参数,并返回VersatileLambda 。 现在你真的很开心了。

您可以创建一个包装类。 像这样的东西:

 public class FuncExtensionWrap { private readonly Expression> exp; private readonly Func func; public FuncExtensionWrap(Expression> exp) { this.exp = exp; this.func = exp.Compile(); } public Expression> AsExp() { return this; } public Func AsFunc() { return this; } public static implicit operator Expression>(FuncExtensionWrap w) { if (w == null) return null; return w.exp; } public static implicit operator Func(FuncExtensionWrap w) { if (w == null) return null; return w.func; } } 

然后它会像这样使用:

 static readonly FuncExtensionWrap expWrap = new FuncExtensionWrap(i => i == 2); // As expression Expression> exp = expWrap; Console.WriteLine(exp.Compile()(2)); // As expression (another way) Console.WriteLine(expWrap.AsExp().Compile()(2)); // As function Func func = expWrap; Console.WriteLine(func(1)); // As function(another way) Console.WriteLine(expWrap.AsFunc()(2)); 

这是一个解决方法。 它为表达式生成一个显式类(因为编译器无论如何都会使用需要函数闭包的lambda表达式),而不仅仅是一个方法,它在静态构造函数中编译表达式,因此它没有任何竞争可能导致多次汇编的条件。 这种解决方法仍会因Compile调用而导致额外的运行时延迟,否则可以将其卸载到构建时,但至少可以保证只使用此模式运行一次。

给定表达式中使用的类型:

 public class SomeClass { public int A { get; set; } public int? B { get; set; } } 

构建一个内部类而不是一个方法,将它命名为方法的任何名称:

 static class SomeClassMeetsConditionName { private static Expression> _expression; private static Func _delegate; static SomeClassMeetsConditionName() { _expression = x => (xA > 3 && !xBHasValue) || (xBHasValue && xBValue > 5); _delegate = _expression.Compile(); } public static Expression> Expression { get { return _expression; } } public static Func Delegate { get { return _delegate; } } } 

然后,您只需传递SomeClassMeetsConditionName然后传递.Delegate.Expression ,而不是使用Where( SomeClassMeetsConditionName() ) ,具体取决于上下文:

 public void Test() { IEnumerable list = GetList(); IQueryable repo = GetQuery(); var r0 = list.Where( SomeClassMeetsConditionName.Delegate ); var r1 = repo.Where( SomeClassMeetsConditionName.Expression ); } 

作为一个内部类,它可以像方法一样被赋予一个访问级别,就像一个方法一样被访问,甚至像方法一样折叠,所以如果你能看到类而不是方法,那么这是function性的解决方法。 甚至可以将其制作成代码模板。