注意:在发布此问题之前,我发现有一种更好的方式来做我想要完成的事情(我觉得它很愚蠢):
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;
}
然而,在ProductTypesList
(CheckedListBox
)中检查项目“权益”和“ETF”。然后由于某种原因,以下代码仅返回“ETF”类型的产品:
var filter = GetProductTypeFilter();
IEnumerable<Product> filteredProducts = allProducts.Where(filter);
我猜测它可能与某些自我引用的混乱有关,其中filter
被设置为,本质上,或其他东西。而且我认为可能会使用......
filter = new Func<Product, bool>(p => (prevFilter(p) || p.ProductType == pt));
......会做的伎俩,但没有这样的运气。谁能看到我在这里失踪的东西?
答案 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
将用于所有相等检查。