有没有办法捕获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中获得Func
和Expression
。 但是,您至少可以封装差异,也可以将Func
的编译推迟到第一次使用时。 这是一个充分利用并满足您需求的解决方案,即使它并不能完全满足您的需求(对Expression
和Func
编译时评估)。
请注意,这可以在不使用[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性的解决方法。 甚至可以将其制作成代码模板。