enumerables c# linq list lambda ienumerable

enumerables - ienumerable methods c#



Un elemento en IEnumerable no es igual a un elemento en la Lista (3)

Simplemente no puedo entender por qué no se encuentra el elemento en mi lista filtrada. He simplificado el ejemplo para mostrarlo. Tengo un artículo de clase ...

public class Item { public Item(string name) { Name = name; } public string Name { get; set; } public override string ToString() { return Name; } }

... y una clase ''Elementos'' que debería filtrar los elementos y verificar si el primer elemento aún está en la lista ...

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

El código de ejecución se ve así:

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

Ahora, si ejecuto el programa, Console.WriteLine genera que el elemento no se encuentra. ¿Pero por qué?

Si cambio la primera línea en el constructor a

_items = items.ToList()

Entonces, puede encontrarlo. Si deshago esa línea y llamo a ToList () más adelante en el método de filtro, ¡¿tampoco puede encontrar el elemento ?!

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

¿Por qué hay una diferencia dónde y cuándo se ejecuta la expresión lambda y por qué ya no se encuentra el elemento? No lo entiendo


Hay dos problemas en su código.

El primer problema es que estás inicializando un nuevo elemento cada vez. Es decir, no almacenas los elementos reales aquí cuando escribes.

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

La ejecución de Select se aplaza. es decir, cada vez que llama a .ToList() se crea un nuevo conjunto de elementos utilizando itemNames como origen.

El segundo problema es que estás comparando artículos por referencia aquí.

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

Cuando utiliza ToList , almacena los elementos en la lista y las referencias siguen siendo las mismas, por lo que encontrará el elemento con referencia.

Cuando no usa ToList las referencias ya no son las mismas. porque cada vez que se crea un nuevo artículo. No puedes encontrar tu artículo con otra referencia.


La razón es la ejecución diferida .

Inicializa el campo _items a

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

Esta es una consulta , no la respuesta a esa consulta. Esta consulta se ejecuta cada vez que se itera sobre _items .

Así que en esta línea de su método de Filter :

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

la matriz de origen se enumera y se crea un new Item(x) para cada cadena. Estos elementos se almacenan en su lista de ret .

Cuando llama a Contains(_items.First()) después de eso, First() ejecuta de nuevo la consulta en _items , creando nuevas instancias de Item para cada cadena de origen.

Dado que el método Equals Item probablemente no se anula y realiza una simple verificación de igualdad de referencia, el primer Item devuelto de la segunda iteración es una instancia de Item diferente a la de su lista.


Vamos a eliminar el código extra para ver el problema:

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

La colección items1 parece no contener su elemento inicial! ( demo )

Al agregar ToList() soluciona el problema:

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

La razón por la que ve diferentes resultados con y sin ToList() es que (1) items1 se evalúa perezosamente y (2) su clase de Item no implementa Equals / GetHashCode . Usar ToList() hace que la igualdad por defecto funcione; la implementación de la verificación de igualdad personalizada solucionaría el problema para la enumeración múltiple.

La lección principal de este ejercicio es que almacenar IEnumerable<T> que se pasa a su constructor es peligroso. Esta es solo una de las razones; otras razones incluyen la enumeración múltiple y la posible modificación de la secuencia después de que su código haya validado su entrada. Debería llamar a ToList o ToArray en secuencias pasadas a constructores para evitar estos problemas:

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