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