【问题标题】:Why do we need iterators in c#?为什么我们需要 C# 中的迭代器?
【发布时间】:2010-11-16 16:05:51
【问题描述】:

有人可以提供一个关于迭代器使用的真实例子吗?我尝试搜索谷歌,但对答案不满意。

【问题讨论】:

  • 你要么不容易满足,要么搜索不够努力。
  • 好的,这是一个愚蠢的问题,但这个“不是问题”是怎么回事?这是我整个星期见过的最明显的愚蠢问题之一。
  • 为什么你会陷入困境,找不到前进的道路,你称之为迭代器。当厄运对你不利并且你没有其他人可以求助时,你就打电话给枚举者。
  • @Closers:来吧,伙计们,我认为如果您要关闭不是一个真正的问题,您有义务添加评论说明原因。否则,我不得不得出结论,你不明白“不是真正的问题”是什么意思。
  • @John Saunders:我不同意。如果由于某种原因您不认识供应商怎么办?对我来说,即使我知道它们是 MSDN 文章,我也会先搜索 Google(毕竟 Google 搜索框始终准备就绪),而且在大多数情况下,无论如何我最终都会直接链接到正确的 MSDN 文章。

标签: c# iterator


【解决方案1】:

您可能听说过数组和容器 - 存储其他对象列表的对象。

但是为了让一个对象表示一个列表,它实际上并不需要“存储”这个列表。它所要做的就是为您提供允许您获取列表项的方法或属性。

在 .NET 框架中,接口 IEnumerable 是对象必须支持的所有内容,才能在这个意义上被视为“列表”。

为了简化一点(省略一些历史包袱):

public interface IEnumerable<T>
{
    IEnumerator<T> GetEnumerator();
}

所以你可以从中得到一个枚举器。该界面(再次,稍微简化以消除干扰噪音):

public interface IEnumerator<T>
{
    bool MoveNext();
    T Current { get; }
}

所以要遍历一个列表,你可以这样做:

var e = list.GetEnumerator();
while (e.MoveNext())
{
    var item = e.Current;

    // blah
}

foreach 关键字巧妙地捕捉到了这种模式:

foreach (var item in list)
    // blah

但是创建一个新的列表呢?是的,我们可以使用List&lt;T&gt; 并用项目填充它。但是,如果我们想在请求时“即时”发现项目怎么办?这样做有一个好处,就是客户端可以在前三项之后放弃迭代,而不必“支付成本”生成整个列表。

手动实现这种惰性列表会很麻烦。我们必须编写两个类,一个通过实现IEnumerable&lt;T&gt; 来表示列表,另一个通过实现IEnumerator&lt;T&gt; 来表示一个活动的枚举操作。

迭代器方法为我们完成了所有艰苦的工作。我们只写:

IEnumerable<int> GetNumbers(int stop)
{
    for (int n = 0; n < stop; n++)
        yield return n;
}

编译器为我们将其转换为两个类。调用该方法相当于构造一个表示列表的类的对象。

【讨论】:

  • 但是如果我想要历史包袱和干扰噪音怎么办?
【解决方案2】:

迭代器是一种抽象,它将集合中的位置概念与集合本身分离。迭代器是一个单独的对象,它存储了在集合中定位一个项目并移动到集合中的下一个项目所需的状态。我见过将状态保留在集合中的集合(即当前位置),但通常最好将该状态移动到外部对象。除其他外,它使您可以让多个迭代器迭代同一个集合。

【讨论】:

    【解决方案3】:

    简单示例:生成整数序列的函数:

    static IEnumerable<int> GetSequence(int fromValue, int toValue)
    {
        if (toValue >= fromValue)
        {
            for (int i = fromValue; i <= toValue; i++)
            {
                yield return i;
            }
        }
        else
        {
            for (int i = fromValue; i >= toValue; i--)
            {
                yield return i;
            }
        }
    }
    

    要在没有迭代器的情况下执行此操作,您需要创建一个数组然后枚举它...

    【讨论】:

    • 如果没有迭代器,你将如何枚举它? :p
    • @Svish : 使用 枚举器,而不是迭代器 ;)
    • 代码真的适用于降序吗?你正在测试i &lt;= toValuedirection 的目的是什么?
    • @MarkJ,很好发现!我没有测试这段代码,显然它不适用于降序。我会在一分钟内修复它
    • @Thomas 很高兴有帮助!还值得一提的是,System.Linq 包含一种通过延迟执行生成整数升序序列的方法,Enumerable.Range 无论如何都要 +1
    【解决方案4】:

    遍历班级中的学生

    迭代器设计模式提供 我们用一种通用的枚举方法 项目或数组的列表,同时隐藏 列表的详细信息 执行。这提供了一个 更清洁地使用数组对象和 隐藏不必要的信息 客户,最终导致 更好的代码重用,增强 可维护性和更少的错误。这 迭代器模式可以枚举 项目清单,无论他们的 实际存储类型。

    【讨论】:

    • 为什么不创建一个学生数组并使用索引。
    • 为什么不根据需要创建尽可能多的学生,每个学生都有自己的变量
    • 为什么不将它们也设为全局变量,以便在程序中的任何位置引用它们?
    • @Bryan:我的大学有 20,000 多名学生。如果您想制作那么多变量,请继续。 @Rohit:如果不是学生而是文件系统上以树形模式排列的文件怎么办?这棵树可能存储在一个数组中,但你必须做很多索引技巧才能让它正确。
    • @OwenP:希望不是所有人都在一个班级:)
    【解决方案5】:

    遍历一组作业问题。

    但说真的,迭代器可以提供一种统一的方式来遍历集合中的项目,而不管底层数据结构如何。

    阅读前两段here 了解更多信息。

    【讨论】:

      【解决方案6】:

      它们非常适合做几件事:
      a) 在保持代码整洁的同时获得“感知性能” - 与其他处理逻辑分离的事物的迭代。
      b) 当您要迭代的项目数量未知时。

      虽然两者都可以通过其他方式完成,但使用迭代器可以使代码变得更好更整洁,因为调用迭代器的人无需担心它如何找到要迭代的内容...

      现实生活中的示例:枚举目录和文件,并找到满足某些条件的第一个 [n],例如包含某个字符串或序列等的文件...

      【讨论】:

        【解决方案7】:

        除此之外,遍历惰性类型序列 - IEnumerators。这种序列的每个下一个元素都可以在迭代步骤中进行评估/初始化,这使得使用有限数量的资源迭代无限序列成为可能......

        【讨论】:

          【解决方案8】:

          典型且最简单的示例是,它使无限序列成为可能,而不必自己编写类来实现这一点的复杂性:

          // generate every prime number
          public IEnumerator<int> GetPrimeEnumerator()
          {
              yield return 2;
              var primes = new List<int>();
              primesSoFar.Add(2);
              Func<int, bool> IsPrime = n => primes.TakeWhile(
                  p => p <= (int)Math.Sqrt(n)).FirstOrDefault(p => n % p == 0) == 0;
          
              for (int i = 3; true; i += 2)
              {
                  if (IsPrime(i))
                  {
                      yield return i;
                      primes.Add(i);
                  }
              }
          }
          

          显然,除非您使用 BigInt 而不是 int,否则这不会是真正的无限,但它给了您这个想法。 为每个生成的序列编写此代码(或类似代码)将是乏味且容易出错的。迭代器会为您执行此操作。如果上面的例子对你来说太复杂了:

          // generate every power of a number from start^0 to start^n
          public IEnumerator<int> GetPowersEnumerator(int start)
          {   
              yield return 1; // anything ^0 is 1
              var x = start;
              while(true)
              {
                  yield return x;
                  x *= start;
              }      
          }
          

          不过,它们是有代价的。它们的懒惰行为意味着在第一次使用生成器之前,您无法发现常见错误(空参数等),而不是在没有编写包装函数首先检查的情况下创建。如果递归使用,当前的实现也难以置信糟糕(1)。

          在树和对象图等复杂结构上编写枚举更容易编写,因为状态维护主要为您完成,您只需编写代码即可访问每个项目,而不必担心返回给它。


          1. 我不会轻易使用这个词 - O(n) 迭代可以变成 O(N^2)

          【讨论】:

            【解决方案9】:

            迭代器是实现 IEnumerator 接口的一种简单方法。无需创建具有接口所需方法和属性的类,只需创建一个逐个返回值的方法,编译器就会创建一个具有实现接口所需方法和属性的类。

            例如,如果您有一个很大的数字列表,并且您想要返回一个每个数字都乘以 2 的集合,您可以创建一个返回数字的迭代器,而不是在内存中创建列表的副本:

            public IEnumerable<int> GetDouble() {
               foreach (int n in originalList) yield return n * 2;
            }
            

            在 C# 3 中,您可以使用扩展方法和 lambda 表达式执行类似的操作:

            originalList.Select(n => n * 2)
            

            或者使用 LINQ:

            from n in originalList select n * 2
            

            【讨论】:

              【解决方案10】:
              IEnumerator<Question> myIterator = listOfStackOverFlowQuestions.GetEnumerator();
              while (myIterator.MoveNext())
              {
                Question q;
                q = myIterator.Current;
                if (q.Pertinent == true)
                   PublishQuestion(q);
                else
                   SendMessage(q.Author.EmailAddress, "Your question has been rejected");
              }
              
              
              foreach (Question q in listOfStackOverFlowQuestions)
              {
                  if (q.Pertinent == true)
                      PublishQuestion(q);
                  else    
                      SendMessage(q.Author.EmailAddress, "Your question has been rejected");
              }
              

              【讨论】:

              • 我支持这一点,但如果你将它重写为迭代器,我会删除我的反对意见。
              • 不完全正确。创建了一个隐式迭代器。见en.wikipedia.org/wiki/Iterator#Implicit_iterators
              • 仍然不是迭代器。我建议您尝试我在问题的 cmets 中为 Rohit 发布的链接。
              • Max - 一些语言称之为迭代器。具体来说,问题是指通常称为生成器的“迭代器块”。在 f# 中,它们被称为“序列表达式”。 java 中的迭代器几乎完全映射到.net IEnumerable。大多数 c# 开发人员会使用“迭代器”来表示迭代器块,其收益率返回(参见我的示例)rateghr 而不是简单的枚举器实例。
              猜你喜欢
              • 2016-08-01
              • 2017-04-17
              • 2013-04-06
              • 2013-06-22
              • 1970-01-01
              • 2018-03-19
              • 2010-09-20
              • 1970-01-01
              • 2019-06-09
              相关资源
              最近更新 更多