tags:

views:

52

answers:

4

The following snippet prints 1 through 10 on the console, but does not terminate until variable 'i' reaches int.MaxValue. TIA for pointing out what I am missing.

class Program 
{
    public static IEnumerable<int> GetList() 
    {
        int i = 0;
        while (i < int.MaxValue)
        {
            i++;
            yield return i;
        }
    }

    static void Main(string[] args)
    {
        var q = from i in GetList() // keeps calling until i reaches int.MaxValue
                where i <= 10
                select i; 

        foreach (int i in q)
            Console.WriteLine(i);
    }
}
+2  A: 

Your iterators limits are 0 through Int32.MaxValue, so it will process that whole range. Iterators are only smart enough to not pre-iterate the results of the range of data you design it to iterate. However they are not smart enough to know when the code that uses them no longer needs more unless you tell it so (i.e. you break out of a foreach loop.) The only way to allow the iterator to limit itself is to pass in the upper bound to the GetList function:

public static IEnumerable<int> GetList(int upperBound) 
{
    int i = 0;
    while (i < upperBound)
    {
        i++;
        yield return i;
    }
}

You could also explicitly tell the iterator that you only wish to iterate the first 10 results:

var numbers = GetList().Take(10);
jrista
The iterator actually "knows" when the code is done looping - the calling code simply stops calling `MoveNext`, resp. `Current` and yielding stops. The problem with the OP's code is that it is a query that simply applies the `i <= 10)` predicate to the iterator. Writing all the records then mean iterating over all the data. The story would be different if the query was executed as an expression (like Linq to SQL, etc.). So if he used `foreach (int i in q) { if (i >= 10) break; Console.WriteLine(i); }`, it would only yield 10 times.
Jaroslav Jandek
@Jaroslav: The **calling code** has to actually stop the iteration, though, the iterator can't stop itself. That was my point. The iterator does not know intrinsically that the calling code only wishes to iterate from 1 to 10.
jrista
@jrista: I agree, I just wanted to explain in detail what is going on - I've got unclear implications from your answer. Your comment is clearer than your original answer (IMHO).
Jaroslav Jandek
@Jaroslav: Aye, I updated the answer to be more clear. Apologies for the confusion.
jrista
+5  A: 

You could try:

        var q = GetList ().TakeWhile ((i)=> i <=10);
Nescio
+1  A: 

Consider using the LINQ extension method .Take() with your argument instead of having it in your where clause. More on Take.

 var q = from i in GetList().Take(10)
                select i;
p.campbell
+5  A: 

The query that you defined in Main doesn't know anything about the ordering of your GetList method, and it must check every value of that list with the predicate i <= 10. If you want to stop processing sooner, you will you can use the Take extension method or use the TakeWhile extension method:

foreach (int i in GetList().Take(10))
    Console.WriteLine(i);

foreach (int i in GetList().TakeWhile(x => x <= 10))
    Console.WriteLine(i);
Steven