【问题标题】:Why does the IEnumerable<T> successfully enumerate twice without me doing anything to reset it?为什么 IEnumerable<T> 成功枚举了两次,而我没有做任何事情来重置它?
【发布时间】:2017-08-30 06:58:55
【问题描述】:

我今天在为拉取请求进行更改时注意到了一些有趣的事情。以下是我的代码:

    public List<MatColor> Colors { get; set; }

    public List<Customer> Customers { get; set; }

    public ProductViewModel()
    {
        this.Colors = new List<MatColor>();
        this.Customers = new List<Customer>();

        var products = this.LoadProductViewList();

        this.LoadColorComboBox(products);
        this.LoadCustomerComboBox(products);
    }

    public void LoadColorComboBox(IEnumerable<Product> products)
    {
        this.Colors.Clear();

        this.Colors = products.Select(p => new MatColor()
        { Code = p.ColorCode, Description = p.ColorDescription })
                                            .DistinctBy(p => m.Code).DistinctBy(p => p.Description).ToList();
    }

    private void LoadCustomerComboBox(IEnumerable<Product> products)
    {
        this.Customers.Clear();

        this.Customers = products.Select(p => new Customer()
        { Name = p.CustomerName, Number = p.CustomerNumber })
                                              .DistinctBy(p => p.Number).DistinctBy(p => p.Name).ToList();
    }

这段代码做了我想做的一切。它成功地填充了ColorsCustomers 列表。我理解为什么它总是能成功填充Colors 列表。那是因为LoadColorComboBox(...) 首先被调用。

但是IEnumerable&lt;T&gt; 只能被枚举,ONCE,对吧?因此,一旦它在LoadColorComboBox(...) 中被枚举,它是如何成功重置并因此在LoadCustomerComboBox(...) 中再次枚举的?我已经检查了LoadProductViewList() 返回的底层类型——它调用了一个返回Task&lt;IEnumerable&lt;Product&gt;&gt; 的REST 服务。我的IEnumerable&lt;Product&gt; 是否以某种方式作为值传递?它不是原始的,所以我的印象是它是一个引用类型,因此,默认情况下会通过引用传入,这会导致第二种方法崩溃。谁能告诉我这里没有看到什么?

【问题讨论】:

  • IEnumerable&lt;T&gt; 可以枚举任意多次。我不确定你从哪里得到它不可能的想法。
  • 为什么一个 IEnumerable 只能被枚举一次。它只是可枚举集合最简化的接口之一。 List 也实现了 IEnumerable 并且可以无限次枚举。您可能会将枚举与延迟执行混淆。这大多发生一次。
  • @BjörnBoxstart 你所说的延迟执行只发生一次是什么意思?延迟执行只是意味着在需要之前不执行计算;如果您多次迭代延迟执行的序列,它将多次执行这些计算。
  • @BJ Myers -- 我正在研究答案,因为我不了解 IEnumerable 与其他类型的集合(如 IList)的所有更精细的工作方式。无论如何,尽管遗憾地问了这个问题,但我不确定我从哪里得到一个 IEnumerable 只能在没有某种类型的位置重置的情况下枚举一次的想法。
  • 对我的问题的仇恨仍在继续,但这是我从哪里得到“你只能枚举一个 IEnumerable 一次”的想法,所以人们明白我的想法起源于哪里:stackoverflow.com/questions/8240844/…

标签: c# wpf ienumerable


【解决方案1】:

IEnumerable 是一个带有GetEnumerator 方法的对象,它能够为您提供一个枚举器。通常人们会期望它能够为您提供任意数量的枚举器。一些特定的实现可能不支持它,但接口的约定是它应该能够为您提供任意数量的。

至于它吐出的IEnumerator 实例,它们确实在技术上具有Reset 方法,应该将其设置回序列的“开始”。然而,在实践中,大多数实现往往不支持“reset”操作符,而只是在调用方法时抛出异常。

在您的情况下,您实际上并没有重置 IEnumerator 并尝试使用它来获取序列的值两次(这是行不通的,因为您使用的 LINQ 方法中没有任何迭代器用于创建您的序列支持被重置)。您所做的只是获取多个不同的枚举器,这些 LINQ 方法都支持。

【讨论】:

  • 感谢您为我详细说明。
  • ...特别是指出重置方法。我发誓我之前收到过错误(在 QuickWatch 期间的数量并不微不足道),其中消息指出 Enumerable(或者它可能是“Enumerator”?)已经被枚举 - 因此我得到的印象是 IEnumerable 只能是枚举一次。
【解决方案2】:

但是 IEnumerable 只能枚举一次,对吧?

没有。 IEnumerable&lt;T&gt; 可以被枚举任意次数。但是对于每个枚举,每个项目只能yielded一次。

在您的情况下,对于需要产品列表的每个 LINQ 查询,products 将被枚举一次......所以在 LoadColorComboBoxLoadCustomerComboBox 中一次。

【讨论】:

  • 您可以创建一个IEnumerator 来多次生成一个项目。就像new []{ "hi", "hi"} 一样简单。重复它,你会得到两次相同的值。
  • 具有相同值的两个项目 =/= 两次相同的项目。虽然 "hi" 作为字符串常量会被保留,但有两个不同的内存位置指向同一个字符串,并且枚举器将每个位置都返回一次。
  • IEnumerator 没有“项目”的概念。它有它产生的价值。而已。它可以产生不同的、独特的或任何它想要的值。你可以编写一个枚举器来产生任何东西。
  • 当然。这是你的代码。您还可以编写一个使 O/S 崩溃的实现。但是,我建议 IEnumerable&lt;T&gt; 的实现使 O/S 崩溃或两次产生相同的项目都存在实现缺陷。值得庆幸的是,您的示例使用了没有任何问题的 CLR 类型,所以我坚持我的回答。
  • 再一次,在处理IEnumerator 时,“项目”的idea 并不存在。声明本身没有任何意义。当然,如果您有一个 IEnumerator 旨在迭代具有某些项目概念的集合,那么这样的迭代器可能会被设计为仅产生集合中的每个项目一次,但是有 lots 的枚举器与集合没有有关,因此没有“项目”的概念。您的陈述仅适用于实际枚举数的一小部分,但您错误地将其声明为对所有枚举数的一般性。
猜你喜欢
  • 2021-08-11
  • 2021-12-20
  • 1970-01-01
  • 2020-08-21
  • 2011-07-25
  • 2015-06-30
  • 1970-01-01
  • 2021-03-16
  • 1970-01-01
相关资源
最近更新 更多