为什么我们需要在c#中使用迭代器?

时间:2009-08-04 12:40:40

标签: c# iterator

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

10 个答案:

答案 0 :(得分:15)

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

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

在.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<T>并填写项目。但是,如果我们想要在需要时“动态”发现这些物品呢?这样做有一个好处,即客户端可以在前三个项目之后放弃迭代,并且他们不必“支付生成整个列表的成本”。

手动实现这种懒惰列表会很麻烦。我们必须编写两个类,一个用于通过实现IEnumerable<T>来表示列表,另一个用于通过实现IEnumerator<T>来表示活动的枚举操作。

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

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

编译器将此转换为两个类。调用该方法等同于构造表示该列表的类的对象。

答案 1 :(得分:12)

迭代器是一种抽象,它将集合中的位置概念与集合本身分离。迭代器是一个单独的对象,存储必要的状态以定位集合中的项目并移动到集合中的下一个项目。我已经看到集合在集合中保持该状态(即当前位置),但通常最好将该状态移动到外部对象。除此之外,它还允许您使用多个迭代器迭代相同的集合。

答案 2 :(得分:5)

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

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;
        }
    }
}

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

答案 3 :(得分:4)

通过课堂上的学生进行迭代

  

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

答案 4 :(得分:3)

迭代一系列作业问题。

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

阅读前两段here以获取更多信息。

答案 5 :(得分:2)

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

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

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

答案 6 :(得分:2)

除了其他一切,迭代懒惰型序列 - IEnumerators。可以在迭代步骤中评估/初始化这种序列的每个下一个元素,这使得可以使用有限量的资源迭代无限序列......

答案 7 :(得分:2)

规范和最简单的例子是,它使无限序列成为可能,而不必自己编写类来完成这一过程的复杂性:

// 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)

答案 8 :(得分:1)

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

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

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

答案 9 :(得分:1)

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");
}