谓词中的闭包,委托在编译时未知

发布于 2024-12-05 02:57:11 字数 2006 浏览 1 评论 0原文

我有以下场景:

一种获取特定类别中低于某个身高阈值的人员列表的方法。我正在使用Where 扩展方法来过滤掉它们。由于谓词依赖于一些外部数据,因此 classIdheight 变量成为 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

陌路终见情 2024-12-12 02:57:12

这对你不起作用吗?

public List<Person> getPeopleFromClassLowerThanLimit(int classId, int height)
{
    Database db = new Database();
    return db.Persons.Where(p => isLowerThan(p, classId, height)).ToList();
}

private bool isLowerThan(Person person, int classId, int height)
{

}

Doesn't this work for you?

public List<Person> getPeopleFromClassLowerThanLimit(int classId, int height)
{
    Database db = new Database();
    return db.Persons.Where(p => isLowerThan(p, classId, height)).ToList();
}

private bool isLowerThan(Person person, int classId, int height)
{

}
肥爪爪 2024-12-12 02:57:12

为什么不简单地调用您的方法作为普通 where 方法的谓词:

public List<Person> getPeopleFromClassShorterThanLimit(int classId, int height)
{
    Database db = new Database();
    return db.Persons.Where(x=>isFromClassShorterThanLimit(classId, height)).ToList();
}

Why not simply call your method as predicate of the normal where method:

public List<Person> getPeopleFromClassShorterThanLimit(int classId, int height)
{
    Database db = new Database();
    return db.Persons.Where(x=>isFromClassShorterThanLimit(classId, height)).ToList();
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文