Domanda Un elemento in IEnumerable non è uguale a un elemento in List


Non riesco proprio a capire perché l'elemento nella mia lista filtrata non è stato trovato. Ho semplificato l'esempio per mostrarlo. Ho un articolo di classe ...

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

    public string Name
    {
        get; set;
    }

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

... e una classe "Articoli" che dovrebbe filtrare gli articoli e controllare se il primo elemento è ancora nell'elenco ...

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

Il codice di esecuzione è simile al seguente:

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

Ora, se eseguo il programma, Console.WriteLine restituisce che l'elemento non è stato trovato. Ma perché?

Se cambio la prima riga nel costruttore in

 _items = items.ToList()

quindi, può trovarlo. Se annullo questa linea e chiamo ToList () più avanti nel metodo Filter, non riesco a trovare l'oggetto ?!

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

Perché c'è una differenza dove e quando viene eseguita l'espressione lambda e perché l'elemento non viene trovato più? Non capisco!


16
2017-12-21 15:20


origine


risposte:


Il motivo è esecuzione differita.

Intializzi il _items campo a

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

Questo è un domanda, non il risposta a quella domanda. Questa query è eseguito ogni volta che si itera sopra _items.

Quindi in questa linea del tuo Filter metodo:

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

l'array sorgente è enumerato e a new Item(x) creato per ogni stringa. Questi articoli sono memorizzati nella tua lista ret.

Quando chiami Contains(_items.First()) dopo di che, First()  ancora esegue il domanda in _items, creando nuovo  Item istanze per ciascuna stringa di origine.

Da Item'S Equals il metodo probabilmente non è sovrascritto ed esegue un semplice controllo di uguaglianza di riferimento, il primo Item restituito dalla seconda iterazione è una diversa istanza di Item di quello nella tua lista.


20
2017-12-21 15:29



Rimuoviamo il codice aggiuntivo per vedere il problema:

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

La collezione items1 sembra non contenere il suo elemento iniziale! (dimostrazione)

Aggiunta ToList() risolve il problema:

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

La ragione per cui vedi risultati diversi con e senza ToList() è quello (1) items1 viene valutato pigramente e (2) il tuo Item la classe non implementa Equals/GetHashCode. utilizzando ToList() rende il lavoro di uguaglianza predefinito; l'implementazione del controllo di uguaglianza personalizzato risolverebbe il problema per l'enumerazione multipla.

La lezione principale di questo esercizio è la memorizzazione IEnumerable<T> che è passato al tuo costruttore è pericoloso. Questo è solo uno dei motivi; altri motivi includono l'enumerazione multipla e la possibile modifica della sequenza dopo il tuo codice ha convalidato il suo input. Dovresti chiamare ToList o ToArray sulle sequenze passate nei costruttori per evitare questi problemi:

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

10
2017-12-21 15:31



Ci sono due problemi nel codice.

Primo problema è che si sta inizializzando un nuovo oggetto ogni volta. Questo è che non si memorizzano gli oggetti reali qui quando scrivi.

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

L'esecuzione di Select è differito. Ogni volta che chiami .ToList() un nuovo set di oggetti viene creato usando itemNames come fonte.

Secondo problema è che stai confrontando gli articoli per riferimento qui.

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

Quando usi ToList memorizzi gli articoli nella lista e i riferimenti rimangono gli stessi in modo da trovare l'articolo con riferimento.

Quando non usi ToList i riferimenti non sono più gli stessi. perché ogni volta viene creato un nuovo elemento. non puoi trovare il tuo oggetto con riferimenti diversi.


4
2017-12-21 15:33