我在这个谓词链中缺少什么?

发布于 2024-08-24 05:25:18 字数 1376 浏览 8 评论 0原文

注意:在发布这个问题之前,我突然想到有一种更好的方法可以完成我想要完成的任务(我觉得这很愚蠢):

IEnumerable<string> checkedItems = ProductTypesList.CheckedItems.Cast<string>();
filter = p => checkedItems.Contains(p.ProductType);

所以好吧,是的,我已经意识到了这一点。然而,我还是发布了这个问题,因为我仍然不太明白为什么我(愚蠢地)尝试所做的事情不起作用。


我认为这会非常容易。事实证明这让我很头疼。

基本思想:在 CheckedListBox 中显示其 ProductType 属性值被选中的所有项目。

实现:

private Func<Product, bool> GetProductTypeFilter() {
    // if nothing is checked, display nothing
    Func<Product, bool> filter = p => false;

    foreach (string pt in ProductTypesList.CheckedItems.Cast<string>()) {
        Func<Product, bool> prevFilter = filter;
        filter = p => (prevFilter(p) || p.ProductType == pt);
    }

    return filter;
}

但是,假设项目“Equity”和“ETF”都在 ProductTypesListCheckedListBox)中检查。然后由于某种原因,以下代码仅返回“ETF”类型的产品:

var filter = GetProductTypeFilter();
IEnumerable<Product> filteredProducts = allProducts.Where(filter);

我猜它可能与一些自引用混乱有关,其中 filter 本质上设置为自身 或者其他东西。我想也许使用……

filter = new Func<Product, bool>(p => (prevFilter(p) || p.ProductType == pt));

可以解决问题,但没有这样的运气。有人能看到我在这里缺少什么吗?

NOTE: Right before posting this question it occurred to me there's a better way of doing what I was trying to accomplish (and I feel pretty stupid about it):

IEnumerable<string> checkedItems = ProductTypesList.CheckedItems.Cast<string>();
filter = p => checkedItems.Contains(p.ProductType);

So OK, yes, I already realize this. However, I'm posting the question anyway, because I still don't quite get why what I was (stupidly) trying to do wasn't working.


I thought this would be extremely easy. Turns out it is giving me quite a headache.

The basic idea: display all the items whose ProductType property value is checked in a CheckedListBox.

The implementation:

private Func<Product, bool> GetProductTypeFilter() {
    // if nothing is checked, display nothing
    Func<Product, bool> filter = p => false;

    foreach (string pt in ProductTypesList.CheckedItems.Cast<string>()) {
        Func<Product, bool> prevFilter = filter;
        filter = p => (prevFilter(p) || p.ProductType == pt);
    }

    return filter;
}

However, say the items "Equity" and "ETF" are both checked in ProductTypesList (a CheckedListBox). Then for some reason, the following code only returns products of type "ETF":

var filter = GetProductTypeFilter();
IEnumerable<Product> filteredProducts = allProducts.Where(filter);

I guessed it might have had something to do with some self-referencing messiness where filter is set to, essentially, itself or something else. And I thought that maybe using ...

filter = new Func<Product, bool>(p => (prevFilter(p) || p.ProductType == pt));

...would do the trick, but no such luck. Can anybody see what I am missing here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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

发布评论

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

评论(3

顾冷 2024-08-31 05:25:18

我相信您在这里遇到了修改后的闭包问题。pt 参数绑定到 lambda 表达式中,但随着循环的进行而发生变化。重要的是要认识到,当在 lambda 中引用变量时,捕获的是变量,而不是变量的值。

在循环中,这有一个非常重要的后果 - 因为循环变量正在改变,而不是被重新定义。通过在循环内创建变量,您可以为每次迭代创建一个新变量,然后允许 lambda 独立捕获每个变量。

所需的实现是:

foreach (string pt in ProductTypesList.CheckedItems.Cast<string>()) {
    string ptCheck = pt;
    Func<Product, bool> prevFilter = filter;
    filter = p => (prevFilter(p) || p.ProductType == ptCheck);
}

Eric Lippert 已写过有关此具体情况的文章:

另外,请参阅问题访问修改后的闭包(2),很好地解释了闭包变量会发生什么。博客 The Old New Thing 上还有一系列文章对此提出了有趣的观点:

I believe you have a modified closure problem here. The pt parameter is bound into the lambda expression but changes as the loop progresses. It's important to realize the when a variable is referenced in a lambda it is the variable that is captured, not the value of the variable.

In loops this has a very significant ramification - because the loop variable is changing, not being redefined. By creating a variable inside the loop, you are creating a new variable for each iteration - which then alows the lambda to capture each independently.

The desired implementation would be:

foreach (string pt in ProductTypesList.CheckedItems.Cast<string>()) {
    string ptCheck = pt;
    Func<Product, bool> prevFilter = filter;
    filter = p => (prevFilter(p) || p.ProductType == ptCheck);
}

Eric Lippert has written about this specific situation:

Also, see the question Access to Modified Closure (2) for a good explanation of what happens with closure variables. There's also an series of articles on the blog The Old New Thing that has an interesting perspective on this:

梦里泪两行 2024-08-31 05:25:18

这与闭包有关。变量 pt 将始终引用 for 循环的最后一个值。

考虑以下示例,其中输出是预期的,因为它使用作用域位于 for 循环内的变量。

public static void Main(string[] args)
{
    var countries = new List<string>() { "pt", "en", "sp" };

    var filter = GetFilter();

    Console.WriteLine(String.Join(", ", countries.Where(filter).ToArray()));
}

private static Func<string, bool> GetFilter()
{
    Func<string, bool> filter = p => false;

    foreach (string pt in new string[] { "pt", "en" })
    {
        Func<string, bool> prevFilter = filter;

        string name = pt;

        filter = p => (prevFilter(p) || p == name);
    }

    return filter;
}

It has to do with closures. The variable pt will always refer to the last value of the for loop.

Consider the following example where the output is the one expected because it's using a variable that is scoped inside the for loop.

public static void Main(string[] args)
{
    var countries = new List<string>() { "pt", "en", "sp" };

    var filter = GetFilter();

    Console.WriteLine(String.Join(", ", countries.Where(filter).ToArray()));
}

private static Func<string, bool> GetFilter()
{
    Func<string, bool> filter = p => false;

    foreach (string pt in new string[] { "pt", "en" })
    {
        Func<string, bool> prevFilter = filter;

        string name = pt;

        filter = p => (prevFilter(p) || p == name);
    }

    return filter;
}
小矜持 2024-08-31 05:25:18

由于您正在循环并将过滤器类型设置为自身,因此您在每种情况下都将产品类型设置为最后一个 pt。它是一个经过修改的闭包,并且由于它是延迟绑定的,因此您需要在每个循环上复制它,如下所示:

foreach (string pt in ProductTypesList.CheckedItems.Cast<string>()) {
    var mypt = pt;
    Func<Product, bool> prevFilter = filter;
    filter = p => (prevFilter(p) || p.ProductType == mypt);
}

这应该会产生正确的结果,否则最后一个 pt 用于所有相等检查。

Since you're looping and setting the filter type to itself, you're setting the product type to the last pt in each case. It's a modified closure and since it's delay bound, you need to copy it on each loop, like this:

foreach (string pt in ProductTypesList.CheckedItems.Cast<string>()) {
    var mypt = pt;
    Func<Product, bool> prevFilter = filter;
    filter = p => (prevFilter(p) || p.ProductType == mypt);
}

This should result in the right result, otherwise the last pt is used for all equality checks.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文