谓词中的闭包,委托在编译时未知
我有以下场景:
一种获取特定类别中低于某个身高阈值的人员列表的方法。我正在使用Where 扩展方法来过滤掉它们。由于谓词依赖于一些外部数据,因此 classId
和 height
变量成为 lambda 闭包的一部分。
public List<Person> getPeopleFromClassShorterThanLimit(int classId, int height)
{
Database db = new Database();
return db.Persons.Where(p => p.Height <= height && p.ClassId == classId).ToList();
}
问题是,有时谓词变得复杂,所以我想将其提取到另一个方法:
public List<Person> getPeopleFromClassLowerThanLimit(int classId, int height)
{
Database db = new Database();
return db.Persons.Where(isLowerThan).ToList();
}
private bool isLowerThan(Person person)
{
}
但是,这里的闭包成为问题,因为我无法将变量传递给谓词函数。我可以内联委托函数(不使用 lambda),以便谓词看起来像一个函数,但此函数也必须与 Where()
调用位于同一方法中。在大多数情况下都可以,但有时我希望将外部函数(编译时未知)调用为 Where
谓词。
我最接近的解决方案是:
public delegate bool ExtendedPredicate<T, TArg>(T argument, TArg[] arguments);
public static class Extended
{
public static IEnumerable<T> Where<T, TArg>(this IEnumerable<T> collection, ExtendedPredicate<T, TArg> predicate, params TArg[] arguments)
{
foreach (T item in collection)
{
if (predicate(item, arguments))
yield return item;
}
yield break;
}
}
public List<Person> getPeopleFromClassShorterThanLimit(int classId, int height)
{
Database db = new Database();
return db.Persons.Where(isFromClassShorterThanLimit, classId, height).ToList();
}
public bool isFromClassShorterThanLimit(Person person, int[] arguments)
{
return person.Height <= arguments[0] && person.ClassId == arguments[1];
}
但我发现它有点太窄了,而且对于每个扩展方法都必须编写新的重载。除此之外,我猜 EntityFramework 无法将此类表达式转换为合理的 SQL 语句(对此不确定,我不是 EF 如何从 lambda 生成 SQL 的专家,但这是一个疯狂的猜测)。 (可能会或可能不会提供可变数量的参数)
问题:
我想知道是否有任何聪明的方法可以通过常规代表(不是强制将编译时定义的函数用作谓词)而不需要像上面所示的那样的黑客攻击?
I have a following scenario:
A method that gets the list of people from a certain class that are shorter than some height threshold. I am using Where extension method to filter them out. Since the predicate depends on some external data, classId
and height
variables become a part of the lambda closure.
public List<Person> getPeopleFromClassShorterThanLimit(int classId, int height)
{
Database db = new Database();
return db.Persons.Where(p => p.Height <= height && p.ClassId == classId).ToList();
}
The problem is that sometimes the predicate gets complicated so I want to extract it to another method:
public List<Person> getPeopleFromClassLowerThanLimit(int classId, int height)
{
Database db = new Database();
return db.Persons.Where(isLowerThan).ToList();
}
private bool isLowerThan(Person person)
{
}
However, the closure here becomes the problem, because I can't pass the variables to the predicate function. I can make a delegate function inline (not using lambdas) so that the predicate looks like a function, but then this function must also be in the same method as the Where()
call. In most of the scenarios it's ok, but sometimes I want an external function (unknown at compile time) to be called as the Where
predicate.
The closest I came to the solution is this:
public delegate bool ExtendedPredicate<T, TArg>(T argument, TArg[] arguments);
public static class Extended
{
public static IEnumerable<T> Where<T, TArg>(this IEnumerable<T> collection, ExtendedPredicate<T, TArg> predicate, params TArg[] arguments)
{
foreach (T item in collection)
{
if (predicate(item, arguments))
yield return item;
}
yield break;
}
}
public List<Person> getPeopleFromClassShorterThanLimit(int classId, int height)
{
Database db = new Database();
return db.Persons.Where(isFromClassShorterThanLimit, classId, height).ToList();
}
public bool isFromClassShorterThanLimit(Person person, int[] arguments)
{
return person.Height <= arguments[0] && person.ClassId == arguments[1];
}
but I find it somewhat too narrow and also, for each extension method a new overload would have to be written. In addition to all that, I guess the EntityFramework would not be able to convert such expressions into a reasonable SQL statement (not sure about this, I am no expert in how EF generates SQL from lambdas, but that's a wild guess).
(variable number of arguments may or may not be provided)
Question:
What I'd like know to is whether there is any smart way of achieving this with regular delegates (not forcing compile-time defined functions to be used as a predicate) without such hacks as one shown above?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这对你不起作用吗?
Doesn't this work for you?
为什么不简单地调用您的方法作为普通 where 方法的谓词:
Why not simply call your method as predicate of the normal where method: