2016-12-21 52 views
16

Po prostu nie mogę zrozumieć, dlaczego pozycja na mojej przefiltrowanej liście nie została znaleziona. Uprośniłem przykład, aby to pokazać. Mam klasy Item ...Element w IEnumerable nie jest równy pozycji na liście

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

    public string Name 
    { 
     get; set; 
    } 

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

... i klasę „pozycje”, które powinny filtrować elementy i sprawdzić, czy pierwsza pozycja jest wciąż na liście ...

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

Kod wykonujący wygląda następująco:

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

teraz, jeśli uruchomić program, wyjścia Console.WriteLine że element nie zostanie znaleziony. Ale dlaczego?

Jeśli zmienię pierwszy wiersz konstruktora do

_items = items.ToList() 

następnie może go znaleźć. Czy po cofnięciu tej linii i wywołaniu ToList() później w metodzie filtrowania również nie można znaleźć elementu ?!

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

Dlaczego występuje różnica w miejscu i czasie, w którym wykonywane jest wyrażenie lambda i dlaczego nie znaleziono już elementu? Nie rozumiem tego!

+2

Inicjujesz nowy element za każdym razem, gdy wywołasz 'x => new Item (x)); '. używając 'Wybierz' –

+1

Dzięki chłopaki. To takie oczywiste, ale nie widziałem tego. Powinienem rzucić pracę na dzisiaj: D – melwynoo

Odpowiedz

20

Powodem jest odroczona realizacja.

Ty intialize pole _items do

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

To zapytania, nie odpowiedź do danego zapytania. To zapytanie jest wykonywane za każdym razem, gdy przeglądasz ponad _items.

Więc w tym wierszu metodę Filter:

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

tablica źródło jest wyliczone i new Item(x) stworzony dla każdej struny. Te pozycje są przechowywane na liście ret.

Po wywołaniu Contains(_items.First()) potem First()ponownie wykonuje zapytanie w _items, tworząc nowychItem instancje dla każdego ciągu źródłowego.

Od Item „s Equals metoda prawdopodobnie nie jest przesłonięta i wykonuje prostą kontrolę równości odniesienia, pierwszy Item wrócił z drugiej iteracji jest inna instancja Item niż w liście.

10

Niech usunąć dodatkowy kod widzi ten problem:

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

Kolekcja items1 wydaje się nie zawierać swój początkowy elementu!(demo)

Dodawanie ToList() rozwiązuje problem:

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

Powodem widać różne wyniki bez ToList() jest to, że (1) items1 ocenia leniwie, oraz (2) klasa Item nie implementuj Equals/GetHashCode. Używanie ToList() powoduje domyślne działanie równości; wdrożenie niestandardowej kontroli równości rozwiązałoby problem wielokrotnego wyliczania.

Główną lekcją z tego ćwiczenia jest to, że przechowywanie IEnumerable<T>, które jest przekazywane do twojego konstruktora, jest niebezpieczne. To tylko jeden z powodów; inne powody to wielokrotne wyliczanie i możliwa modyfikacja sekwencji po zweryfikowaniu przez twój kod danych wejściowych. Należy zadzwonić ToList lub ToArray na sekwencjach przeszły do ​​konstruktorów, aby uniknąć tych problemów:

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

Istnieją dwa problemy w kodzie.

Pierwszy problem polega na tym, że za każdym razem uruchamiasz nowy element. To znaczy, że nie przechowujesz tutaj rzeczywistych przedmiotów podczas pisania.

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

Realizacja Select zostaje odroczona. to jest za każdym razem, gdy wywołujesz .ToList() nowy zestaw elementów jest tworzony przy użyciu itemNames jako źródła.

Drugi problem polega na porównywaniu pozycji przez odniesienie tutaj.

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

Podczas korzystania ToList zapisać pozycje na liście i odniesienia pozostaje taka sama więc znajdziesz przedmiot z odniesieniem.

Jeśli nie używasz ToList, odniesienia nie są już takie same. ponieważ za każdym razem tworzony jest nowy przedmiot. nie możesz znaleźć przedmiotu z innym odniesieniem.