10

注意:在发布这个问题之前,我突然想到有一种更好的方法来做我想要完成的事情(我对此感到很愚蠢):

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

好吧,是的,我已经意识到了这一点。但是,无论如何我都会发布这个问题,因为我仍然不太明白为什么我(愚蠢地)尝试做的事情没有奏效。


我认为这将非常容易。原来这让我很头疼。

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

实施:

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”都在ProductTypesList(a CheckedListBox) 中签入。然后由于某种原因,以下代码仅返回“ETF”类型的产品:

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

我猜它可能与一些自我引用的混乱filter有关,本质上,它本身其他东西。我想也许使用...

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

......会做的伎俩,但没有这样的运气。任何人都可以看到我在这里缺少什么吗?

4

3 回答 3

9

我相信你在这里有一个修改过的关闭问题。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 曾写过关于这种特定情况的文章:

另外,请参阅问题Access to Modified Closure (2),以很好地解释闭包变量会发生什么。博客The Old New Thing上也有一系列文章对此有一个有趣的观点:

于 2010-03-05T20:06:38.267 回答
2

它与闭包有关。变量 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;
}
于 2010-03-05T20:06:58.927 回答
2

由于您正在循环并将过滤器类型设置为自身,因此您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用于所有相等性检查。

于 2010-03-05T20:07:19.200 回答