IEnumerable 中的项目不等于 List 中的项目
An item in IEnumerable does not equal an item in List
我只是想不通为什么找不到我筛选列表中的项目。我已经简化了这个例子来展示它。我有一个 class 项目...
public class Item
{
public Item(string name)
{
Name = name;
}
public string Name
{
get; set;
}
public override string ToString()
{
return Name;
}
}
... 和 class 'Items' 应该过滤项目并检查第一项是否仍在列表中...
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()
然后,它可以找到它。如果我撤消该行并稍后在 Filter 方法中调用 ToList(),它也找不到该项目?!
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;
}
}
为什么执行 lambda 表达式的时间和地点不同,为什么找不到该项目?我不明白!
原因是延迟执行。
您将 _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
是 [= 的不同实例20=] 比你列表中的那个。
让我们删除额外的代码以查看问题:
var itemNames = new [] { "a", "b", "c" };
var items1 = itemNames.Select(x => new Item(x));
var surprise = items1.Contains(items1.First()); // False
集合 items1
似乎不包含其初始元素! (demo)
添加 ToList()
解决了问题:
var items2 = itemNames.Select(x => new Item(x)).ToList();
var noSurprise = items2.Contains(items2.First()); // True
使用 ToList()
和不使用 ToList()
时您看到不同结果的原因是 (1) items1
被延迟评估,并且 (2) 您的 Item
class 确实未实施 Equals
/GetHashCode
。使用 ToList()
使默认相等性起作用;实施自定义相等性检查将解决多重枚举的问题。
这个练习的主要教训是存储传递给构造函数的 IEnumerable<T>
是危险的。这只是其中一个原因;其他原因包括多重枚举和序列的可能修改 在 您的代码已验证其输入之后。您应该对传递给构造函数的序列调用 ToList
或 ToArray
以避免这些问题:
public Items(IEnumerable<Item> items) {
_items = items.ToList();
}
您的代码中有两个问题。
第一个问题是你每次都在初始化一个新项目。那就是你写的时候不把实际的项目存储在这里。
IEnumerable<Item> items = itemNames.Select(x => new Item(x));
Select
的执行被推迟。也就是说,每次您调用 .ToList()
时,都会使用 itemNames
作为源创建一组新的项目。
第二个问题是您在这里通过引用比较项目。
Console.WriteLine("found: " + ret.Contains(_items.First()));
当您使用 ToList
时,您将项目存储在列表中并且引用保持不变,因此您将找到带有引用的项目。
当您不使用 ToList
时,引用将不再相同。因为每次创建一个新项目。您找不到具有不同参考号的商品。
我只是想不通为什么找不到我筛选列表中的项目。我已经简化了这个例子来展示它。我有一个 class 项目...
public class Item
{
public Item(string name)
{
Name = name;
}
public string Name
{
get; set;
}
public override string ToString()
{
return Name;
}
}
... 和 class 'Items' 应该过滤项目并检查第一项是否仍在列表中...
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()
然后,它可以找到它。如果我撤消该行并稍后在 Filter 方法中调用 ToList(),它也找不到该项目?!
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;
}
}
为什么执行 lambda 表达式的时间和地点不同,为什么找不到该项目?我不明白!
原因是延迟执行。
您将 _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
是 [= 的不同实例20=] 比你列表中的那个。
让我们删除额外的代码以查看问题:
var itemNames = new [] { "a", "b", "c" };
var items1 = itemNames.Select(x => new Item(x));
var surprise = items1.Contains(items1.First()); // False
集合 items1
似乎不包含其初始元素! (demo)
添加 ToList()
解决了问题:
var items2 = itemNames.Select(x => new Item(x)).ToList();
var noSurprise = items2.Contains(items2.First()); // True
使用 ToList()
和不使用 ToList()
时您看到不同结果的原因是 (1) items1
被延迟评估,并且 (2) 您的 Item
class 确实未实施 Equals
/GetHashCode
。使用 ToList()
使默认相等性起作用;实施自定义相等性检查将解决多重枚举的问题。
这个练习的主要教训是存储传递给构造函数的 IEnumerable<T>
是危险的。这只是其中一个原因;其他原因包括多重枚举和序列的可能修改 在 您的代码已验证其输入之后。您应该对传递给构造函数的序列调用 ToList
或 ToArray
以避免这些问题:
public Items(IEnumerable<Item> items) {
_items = items.ToList();
}
您的代码中有两个问题。
第一个问题是你每次都在初始化一个新项目。那就是你写的时候不把实际的项目存储在这里。
IEnumerable<Item> items = itemNames.Select(x => new Item(x));
Select
的执行被推迟。也就是说,每次您调用 .ToList()
时,都会使用 itemNames
作为源创建一组新的项目。
第二个问题是您在这里通过引用比较项目。
Console.WriteLine("found: " + ret.Contains(_items.First()));
当您使用 ToList
时,您将项目存储在列表中并且引用保持不变,因此您将找到带有引用的项目。
当您不使用 ToList
时,引用将不再相同。因为每次创建一个新项目。您找不到具有不同参考号的商品。