简体   繁体   中英

Why do we need iterators in c#?

Can somebody provide a real life example regarding use of iterators. I tried searching google but was not satisfied with the answers.

You've probably heard of arrays and containers - objects that store a list of other objects.

But in order for an object to represent a list, it doesn't actually have to "store" the list. All it has to do is provide you with methods or properties that allow you to obtain the items of the list.

In the .NET framework, the interface IEnumerable is all an object has to support to be considered a "list" in that sense.

To simplify it a little (leaving out some historical baggage):

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

So you can get an enumerator from it. That interface (again, simplifying slightly to remove distracting noise):

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

So to loop through a list, you'd do this:

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

    // blah
}

This pattern is captured neatly by the foreach keyword:

foreach (var item in list)
    // blah

But what about creating a new kind of list? Yes, we can just use List<T> and fill it up with items. But what if we want to discover the items "on the fly" as they are requested? There is an advantage to this, which is that the client can abandon the iteration after the first three items, and they don't have to "pay the cost" of generating the whole list.

To implement this kind of lazy list by hand would be troublesome. We would have to write two classes, one to represent the list by implementing IEnumerable<T> , and the other to represent an active enumeration operation by implementing IEnumerator<T> .

Iterator methods do all the hard work for us. We just write:

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

And the compiler converts this into two classes for us. Calling the method is equivalent to constructing an object of the class that represents the list.

Iterators are an abstraction that decouples the concept of position in a collection from the collection itself. The iterator is a separate object storing the necessary state to locate an item in the collection and move to the next item in the collection. I have seen collections that kept that state inside the collection (ie a current position), but it is often better to move that state to an external object. Among other things it enables you to have multiple iterators iterating the same collection.

Simple example : a function that generates a sequence of integers :

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

To do it without an iterator, you would need to create an array then enumerate it...

Iterate through the students in a class

The Iterator design pattern provides us with a common method of enumerating a list of items or array, while hiding the details of the list's implementation. This provides a cleaner use of the array object and hides unneccessary information from the client, ultimately leading to better code-reuse, enhanced maintainability, and fewer bugs. The iterator pattern can enumerate the list of items regardless of their actual storage type.

Iterate through a set of homework questions.

But seriously, Iterators can provide a unified way to traverse the items in a collection regardless of the underlying data structure.

Read the first two paragraphs here for a little more info.

A couple of things they're great for:
a) For 'perceived performance' while maintaining code tidiness - the iteration of something separated from other processing logic.
b) When the number of items you're going to iterate through is not known.

Although both can be done through other means, with iterators the code can be made nicer and tidier as someone calling the iterator don't need to worry about how it finds the stuff to iterate through...

Real life example: enumerating directories and files, and finding the first [n] that fulfill some criteria, eg a file containing a certain string or sequence etc...

Beside everything else, to iterate through lazy-type sequences - IEnumerators. Each next element of such sequence may be evaluated/initialized upon iteration step which makes it possible to iterate through infinite sequences using finite amount of resources...

The canonical and simplest example is that it makes infinite sequences possible without the complexity of having to write the class to do that yourself:

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

Obviously this would not be truly infinite unless you used a BigInt instead of int but it gives you the idea. Writing this code (or similar) for each generated sequence would be tedious and error prone. the iterators do that for you. If the above example seems too complex for you consider:

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

They come at a cost though. Their lazy behaviour means you cannot spot common errors (null parameters and the like) until the generator is first consumed rather than created without writing wrapping functions to check first. The current implementation is also incredibly bad(1) if used recursively.

Wiriting enumerations over complex structures like trees and object graphs is much easier to write as the state maintenance is largely done for you, you must simply write code to visit each item and not worry about getting back to it.


  1. I don't use this word lightly - a O(n) iteration can become O(N^2)

An iterator is an easy way of implementing the IEnumerator interface. Instead of making a class that has the methods and properties required for the interface, you just make a method that returns the values one by one and the compiler creates a class with the methods and properties needed to implement the interface.

If you for example have a large list of numbers, and you want to return a collection where each number is multiplied by two, you can make an iterator that returns the numbers instead of creating a copy of the list in memory:

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

In C# 3 you can do something quite similar using extension methods and lambda expressions:

originalList.Select(n => n * 2)

Or using LINQ:

from n in originalList select n * 2
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");
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM