Примечание: Прямо прежде, чем отправить этот вопрос это произошло со мной существует лучший способ сделать то, что я пытался выполнить (и я чувствую себя довольно глупым об этом):
IEnumerable<string> checkedItems = ProductTypesList.CheckedItems.Cast<string>();
filter = p => checkedItems.Contains(p.ProductType);
Так хорошо, да, я уже понимаю это. Однако я отправляю вопрос так или иначе, потому что я все еще не вполне добираюсь, почему то, что я (глупо) пытался сделать, не работало.
Я думал, что это будет чрезвычайно легко. Оказывается, что это дает мне настоящую головную боль.
Основная идея: отобразите все объекты чей ProductType
в значении свойства регистрируются a 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;
}
Однако скажите, что в объектах "Акция" и "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));
... добился бы цели, но никакая такая удача. Кто-либо может видеть то, что я пропускаю здесь?
Я полагаю, у вас здесь модифицированная проблема закрытия. Параметр pt
связан с лямбда-выражением, но изменяется по мере выполнения цикла. Важно понимать, что когда на переменную ссылаются в лямбда , фиксируется именно переменная, а не значение переменной .
В циклах это имеет очень важное разветвление - потому что переменная цикла изменяется, а не переопределяется. Создавая переменную внутри цикла , вы создаете новую переменную для каждой итерации, которая затем позволяет лямбда-выражению захватить каждую из них независимо.
Желаемая реализация:
foreach (string pt in ProductTypesList.CheckedItems.Cast<string>()) {
string ptCheck = pt;
Func<Product, bool> prevFilter = filter;
filter = p => (prevFilter(p) || p.ProductType == ptCheck);
}
Эрик Липперт писал об этой конкретной ситуации:
Также см. Вопрос Доступ к модифицированному закрытию (2) для хорошего объяснения того, что происходит с закрывающими переменными. В блоге также есть серия статей The Old New Thing , в которых есть интересный взгляд на это:
Это связано с закрытием. Переменная 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;
}
Поскольку вы выполняете цикл и устанавливаете тип фильтра на себя, вы устанавливаете тип продукта на последний 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
используется для всех проверок равенства.