gpt4 book ai didi

c# - 有没有办法捕获 lambda 表达式,这样它就不会在编译时被迫采用表达式或委托(delegate)类型的身份?

转载 作者:行者123 更新时间:2023-11-30 18:22:51 25 4
gpt4 key购买 nike

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

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

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

lambda 语法本身可用于生成或者 Expression<Func<T,bool>>Func<T,bool> (或与此相关的任何委托(delegate)类型),但在这种情况下,它不能一次生成多个委托(delegate)。

例如,我可以这样写:

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

尽可能简单地写:

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

问题是我不能在两种方式中使用完全相同的 lambda 表达式(即 x => x.A == x.B),而不会将其物理复制到具有两种不同返回类型的两个单独的方法中,尽管编译器能够将其编译成任何一个。

换句话说,如果我想在 Queryable 中使用 lambda 表达式方法,那么我必须使用 Expression方法签名。但是,一旦我这样做了,我就不能将它用作 Func我尽可能简单地将方法返回类型声明为 Func .相反,我现在必须调用 CompileExpression 上然后像这样手动缓存结果:

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

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

最佳答案

不幸的是,我看不出有什么办法可以在编译时真正得到一个Func。和一个 Expression来自同一个 lambda。然而,你至少可以封装掉差异,你也可以推迟 Func 的编译。直到第一次使用它。这是一个充分利用事物并可能满足您需求的解决方案,即使它并没有完全满足您真正想要的(ExpressionFunc 的编译时评估)。

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

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) { }
}

隐式转换的美妙之处在于,在特定 Expression<Func<>> 的上下文中或 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<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

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

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

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

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

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

此外,您可以使用 lambda 无法完成的表达式来做一些非常酷的事情。检查一下:

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);

您可以创建 Chain 的重载这需要 VersatileLambda作为第一个参数,并返回一个 VersatileLambda .现在你真的很兴奋。

关于c# - 有没有办法捕获 lambda 表达式,这样它就不会在编译时被迫采用表达式或委托(delegate)类型的身份?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33399863/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com