【问题标题】:An item in IEnumerable does not equal an item in ListIEnumerable 中的项目不等于 List 中的项目
【发布时间】:2016-12-21 15:20:58
【问题描述】:

我只是想不通为什么在我的过滤列表中找不到该项目。我已经简化了这个例子来展示它。我有一个类 Item...

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

    public string Name
    {
        get; set;
    }

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

...还有一个“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 表达式的位置和时间会有所不同,为什么找不到该项目?没看懂!

【问题讨论】:

  • 每次调用x =&gt; new Item(x)));时,您都在初始化一个新项目。使用Select
  • 谢谢大家。这很明显,但我没有看到。我今天应该辞职:D

标签: c# linq list lambda ienumerable


【解决方案1】:

原因是延迟执行

您将_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中的查询,创建newItem每个源字符串的实例。

由于ItemEquals 方法可能未被覆盖并执行简单的引用相等检查,因此从第二次迭代返回的第一个Item 是与列表中的Item 不同的实例。

【讨论】:

    【解决方案2】:

    让我们去掉多余的代码来看看问题:

    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() 时您看到不同结果的原因是 (1) items1 被延迟评估,以及 (2) 您的 Item 类没有实现 Equals/GetHashCode。使用ToList() 使默认相等工作;实现自定义相等检查将解决多个枚举的问题。

    本练习的主要教训是存储传递给构造函数的IEnumerable&lt;T&gt; 是危险的。这只是其中一个原因;其他原因包括多次枚举以及在您的代码验证其输入之后可能修改序列。您应该在传递给构造函数的序列上调用 ToListToArray 以避免这些问题:

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

    【讨论】:

      【解决方案3】:

      您的代码中有两个问题。

      第一个问题是您每次都在初始化一个新项目。那就是你写的时候没有把实际的项目存储在这里。

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

      Select 的执行被推迟。即每次调用.ToList() 时,都会使用itemNames 作为源创建一组新项目。

      第二个问题是您在这里通过引用比较项目。

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

      当您使用ToList 时,您将项目存储在列表中,并且引用保持不变,因此您将找到具有引用的项目。

      当您不使用ToList 时,引用不再相同。因为每次创建一个新项目。您找不到具有不同参考的商品。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-03
        • 1970-01-01
        • 1970-01-01
        • 2016-06-25
        • 1970-01-01
        • 1970-01-01
        • 2011-01-29
        • 2023-03-22
        相关资源
        最近更新 更多