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 个实例。

由于 ItemEquals 方法可能未被覆盖并执行简单的引用相等性检查,因此从第二次迭代返回的第一个 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> 是危险的。这只是其中一个原因;其他原因包括多重枚举和序列的可能修改 您的代码已验证其输入之后。您应该对传递给构造函数的序列调用 ToListToArray 以避免这些问题:

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 时,引用将不再相同。因为每次创建一个新项目。您找不到具有不同参考号的商品。