Элемент в IEnumerable не равен элементу в списке

Я просто не могу понять, почему элемент в моем отфильтрованном списке не найден. Я упростил пример, чтобы показать его. У меня есть класс...

public class Item
{
    public Item(string name)
    {
        Name = name;
    }

    public string Name
    {
        get; set;
    }

    public override string ToString()
    {
        return Name;
    }
}

... и класс "Элементы", которые должны фильтровать элементы и проверять, находится ли первый элемент в списке...

public class Items
{
    private IEnumerable<Item> _items;

    public Items(IEnumerable<Item> items)
    {
        _items = items;
    }

    public List<Item> Filter(string word)
    {
        var ret = new List<Item>(_items.Where(x => x.Name.Contains(word)));

        Console.WriteLine("found: " + ret.Contains(_items.First()));
        // found: false

        return ret;
    }
}

Исполняющий код выглядит следующим образом:

static void Main(string[] args)
{
    string[] itemNames = new string[] { "a", "b", "c" };

    Items list = new Items(itemNames.Select(x => new Item(x)));
    list.Filter("a");

    Console.ReadLine();
}

Теперь, если я запустил программу, Console.WriteLine выводит, что элемент не найден. Но почему?

Если я изменил первую строчку в конструкторе на

 _items = items.ToList()

то он может ее найти. Если я отменим эту строку и вызову ToList() позже в методе Filter, она также не сможет найти элемент?!

public class Items
{
    private IEnumerable<Item> _items;

    public Items(IEnumerable<Item> items)
    {
        _items = items;
    }

    public List<Item> FilteredItems
    {
        get; set;
    }

    public List<Item> Filter(string word)
    {
        var ret = new List<Item>(_items.Where(x => x.Name.Contains(word)));

        _items = _items.ToList();
        Console.WriteLine("found: " + ret.Contains(_items.First()));
        // found: false

        return ret;
    }
}

Почему существует разница, когда и когда выполняется выражение лямбда и почему элемент не найден? Я не понимаю!

Ответы

Ответ 1

Причина отложенное выполнение.

Вы проиндексируете поле _items в

itemNames.Select(x => new Item(x));

Это запрос, а не ответ на этот запрос. Этот запрос выполняется каждый раз, когда вы перебираете _items.

Итак, в этой строке вашего метода Filter:

var ret = new List<Item>(_items.Where(x => x.Name.Contains(word)));

исходный массив перечисляется и создается new Item(x) для каждой строки. Эти элементы хранятся в вашем списке ret.

Когда вы вызываете Contains(_items.First()) после этого, First() снова выполняет запрос в _items, создавая экземпляры new Item для каждой исходной строки.

Так как метод Item Equals, вероятно, не переопределяется и выполняет простую проверку равенства ссылок, первый Item, возвращенный со второй итерации, является другим экземпляром Item, чем тот, что указан в вашем списке.

Ответ 2

Удалите лишний код, чтобы увидеть проблему:

var itemNames = new [] { "a", "b", "c" };
var items1 = itemNames.Select(x => new Item(x));
var surprise = items1.Contains(items1.First());   // False

Коллекция items1, похоже, не содержит его начального элемента! (демонстрация)

Добавление ToList() устраняет проблему:

var items2 = itemNames.Select(x => new Item(x)).ToList();
var noSurprise = items2.Contains(items2.First()); // True

Причина, по которой вы видите разные результаты с и без ToList(), заключается в том, что (1) items1 оценивается лениво, и (2) ваш класс Item не реализует Equals/GetHashCode. Использование ToList() обеспечивает работу по умолчанию по умолчанию; реализация пользовательской проверки равенства исправит проблему для множественного перечисления.

Основной урок этого упражнения заключается в том, что сохранение IEnumerable<T>, передаваемого вашему конструктору, опасно. Это только одна из причин; другие причины включают множественное перечисление и возможную модификацию последовательности после того, как ваш код подтвердил свой ввод. Вы должны вызвать ToList или ToArray для последовательностей, переданных в конструкторы, чтобы избежать этих проблем:

public Items(IEnumerable<Item> items) {
    _items = items.ToList();
}

Ответ 3

В коде есть две проблемы.

Первая проблема заключается в том, что вы каждый раз инициализируете новый элемент. То есть вы не храните фактические предметы здесь, когда пишете.

IEnumerable<Item> items = itemNames.Select(x => new Item(x));

Выполнение Select отложено. каждый раз, когда вы вызываете .ToList(), новый набор элементов создается с использованием itemNames в качестве источника.

Вторая проблема заключается в том, что вы сравниваете элементы по ссылке здесь.

Console.WriteLine("found: " + ret.Contains(_items.First()));

Когда вы используете ToList, вы храните элементы в списке, и ссылки остаются такими же, поэтому вы найдете элемент со ссылкой.

Если вы не используете ToList, ссылки уже не такие. потому что каждый раз, когда создается новый элемент. вы не можете найти свой товар с другой ссылкой.