Что я пропускаю в этой цепочке предикатов?

Примечание: Прямо прежде, чем отправить этот вопрос это произошло со мной существует лучший способ сделать то, что я пытался выполнить (и я чувствую себя довольно глупым об этом):

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

... добился бы цели, но никакая такая удача. Кто-либо может видеть то, что я пропускаю здесь?

10
задан Dan Tao 6 March 2010 в 06:50
поделиться

3 ответа

Я полагаю, у вас здесь модифицированная проблема закрытия. Параметр 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 , в которых есть интересный взгляд на это:

9
ответ дан 3 December 2019 в 23:49
поделиться

Это связано с закрытием. Переменная 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
ответ дан 3 December 2019 в 23:49
поделиться

Поскольку вы выполняете цикл и устанавливаете тип фильтра на себя, вы устанавливаете тип продукта на последний 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 используется для всех проверок равенства.

2
ответ дан 3 December 2019 в 23:49
поделиться
Другие вопросы по тегам:

Похожие вопросы: