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

时间:2010-03-05 19:57:27

标签: c# .net linq delegates predicate

注意:在发布此问题之前,我发现有一种更好的方式来做我想要完成的事情(我觉得它很愚蠢):

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

然而,在ProductTypesListCheckedListBox)中检查项目“权益”和“ETF”。然后由于某种原因,以下代码仅返回“ETF”类型的产品:

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

我猜测它可能与某些自我引用的混乱有关,其中filter被设置为,本质上,其他东西。而且我认为可能会使用......

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

......会做的伎俩,但没有这样的运气。谁能看到我在这里失踪的东西?

3 个答案:

答案 0 :(得分: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 上还有一系列文章,对此有一个有趣的看法:

答案 1 :(得分: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;
}

答案 2 :(得分: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将用于所有相等检查。