【问题标题】:What am I missing in this chain of predicates?我在这个谓词链中缺少什么?
【发布时间】:2011-01-24 06:41:11
【问题描述】:

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

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

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

【问题讨论】:

    标签: c# .net linq delegates predicate


    【解决方案1】:

    我相信你这里有一个修改过的闭包问题。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 博客上也有一系列文章对此有一个有趣的观点:

    【讨论】:

    • 嗯,这很有意义。谢谢!
    【解决方案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;
    }
    

    【讨论】:

      【解决方案3】:

      由于您正在循环并将过滤器类型设置为自身,因此在每种情况下您都将产品类型设置为最后一个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 将用于所有相等性检查。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-07-01
        • 2021-09-19
        • 1970-01-01
        • 2018-04-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多