views:

92

answers:

3
class Foo
{
    public static IEnumerable<int> Range(int start, int end)
    {
        return Enumerable.Range(start, end);
    }

    public static void PrintRange(IEnumerable<int> r)
    {
        foreach (var item in r)
        {
            Console.Write(" {0} ", item);
        }
        Console.WriteLine();
    }
}

class Program
{
    static void TestFoo()
    {
        Foo.PrintRange(Foo.Range(10, 20));
    }

    static void Main()
    {
        TestFoo();
    }
}

Expected Output:

10  11  12  13  14  15  16  17  18  19  20

Actual Output:

10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29

What is the problem with this code? Whats happening?

+7  A: 

The second parameter of Enumerable.Range specifies the number of integers to generate, not the last integer in the range.

If necessary, it's easy enough to build your own method, or update your existing Foo.Range method, to generate a range from start and end parameters.

LukeH
OMG its horrible!
TheMachineCharmer
Don't forget that this is on Enumerable not int, for a lot (most?) enumerables 'last item in range' isn't going to make much sense.
FinnNk
@FinnNk: The output of `Enumerable.Range` is always an `IEnumerable<int>`. If the `count` argument is zero then the sequence will be empty, otherwise it will contain sequential integers.
LukeH
+3  A: 

Second parameter of Range is the numbers of items to produce.

Anton Gogolev
+2  A: 

Why is it not end but count?

How do you enumerate an empty range if you have start and end points? For example, suppose you have a text buffer on the screen and a selection, and the selection is of a single character starting at character 12 and ending at character 12. How do you enumerate that range? You enumerate one character starting at character 12.

Now suppose the selection is ZERO characters. How do you enumerate it? If you have start, size, you just pass zero for size. If you have start, end, what do you do? You can't pass 12 and 12.

Now you might say "well, just don't enumerate it if its an empty range". So you end up taking code that ought to look like this:

var results = from index in Range(selectionStart, selectionSize)
              where blah blah blah
              select blah;

and instead writing

IEnumerable<Chicken> results = null;
if (selectionSize == 0)
{
    results = Enumerable.Empty<Chicken>();
}
else
{
    results = from index in Range(selectionStart, selectionEnd)
              where blah blah blah
              select blah;
}

which hurts my eyes.

Eric Lippert
You could make the end exclusive and the start inclusive, such that count == end - start, which allows you to represent empty ranges elegantly. (Dijkstra agreed, see EWD831). But I'm pretty happy things are the way they are, so I don't have to remember details about inclusive or exclusive indices.
Joren
Joren: OK, how do you represent a range that ends at Int32.MaxValue then? If the "end" has to be one bigger than the last value in the sequence then you have some choices. (1) don't allow that value to be the last one in the sequence, (2) make the end point a long rather than an int just for this case. Both seem like a bad design.
Eric Lippert
But you've still got a related problem: your range can't extend from -1 to Int32.MaxValue, for instance, since the length would overflow. With Joren's approach, you could actually support a larger number of ranges than in the current approach.
kvb
@kvb: Nicely done; I was wondering if anyone would notice that flaw in my argument! :-) In short, there are pros and cons to both approaches.
Eric Lippert