Чего мне не хватает в этой цепочке предикатов? - PullRequest
10 голосов
/ 05 марта 2010

ПРИМЕЧАНИЕ : Прямо перед публикацией этого вопроса мне пришло в голову, что есть лучший способ сделать то, что я пытался достичь (и я чувствую себя довольно глупо):

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

Тем не менее, скажем, что пункты "Equity" и "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));

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

Ответы [ 3 ]

9 голосов
/ 05 марта 2010

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

2 голосов
/ 05 марта 2010

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

Это связано с замыканиями.Переменная 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;
}
...