views:

887

answers:

12

In C# 3.0, I'm liking this style:

// Write the numbers 1 thru 7
foreach( int index in Enumerable.Range( 1, 7 ) )
{
    Console.WriteLine( index );
}

over the traditional for loop:

// Write the numbers 1 thru 7
for( int index = 1; index <= 7; index++ )
{
    Console.WriteLine( index );
}

Assuming 'n' is small so performance is not an issue, does anyone object to the new style over the traditional style?

Update: Thanks to everyone for their input! I think the main points are:

  • The traditional 'for' loop is well understood, and offers the least surprise to other developers reading your code.
  • Enumerable.Range would be better if it was shorter
  • Enumerable.Range would be better if the parameters were (start, end) instead of (start, count)
  • And if you really want to go to town, you can use an extension method to get "1.To(7) syntax"
+3  A: 

I'm sure everybody has their personal preferences (many would prefer the later just because it is familiar over almost all programming languages), but I am like you and starting to like the foreach more and more, especially now that you can define a range.

TheTXI
+8  A: 

It seems like quite a long winded approach to a problem that's already solved. There's a whole state machine behind the Enumerable range that isn't really needed.

The traditional format is fundamental to development and familiar to all. I don't really see any advantage to your new style.

spender
Possible advantage: Easier to read?
Marcel Lamothe
In some ways. Enumerable.Range becomes just as unreadable when you want to iterate from a min to a max, because the second parameter is a range and needs to be calculated.
spender
Ah, good point, but that's a different case (that I haven't run into yet). Perhaps an extension method with (start, end) instead of (start, count) would help in that scenario.
Marcel Lamothe
+1  A: 

I imagine there could be scenarios where Enumerable.Range(index, count) is clearer when dealing with expressions for the parameters, especially if some of the values in that expression are altered within the loop. In the case of for the expression would be evaluated based on the state after the current iteration, whereas Enumerable.Range() is evaluated up-front.

Other than that, I'd agree that sticking with for would normally be better (more familiar/readable to more people... readable is a very important value in code that needs to be maintained).

jerryjvl
+10  A: 

I find the latter's "minimum-to-maximum" format a lot clearer than Range's "minimum-count" style for this purpose. Also, I don't think it's really a good practice to make a change like this from the norm that is not faster, not shorter, not more familiar, and not obviously clearer.

That said, I'm not against the idea in general. If you came up to me with syntax that looked something like foreach (int x from 1 to 8) then I'd probably agree that that would be an improvement over a for loop. However, Enumerable.Range is pretty clunky.

mquander
I like your proposed syntax!
Marcel Lamothe
With C# 4's named arguments feature, we could add an extension method and call it like this (I don't have a good name for it right now): foreach (int x in Enumerable.Range2(from = 1, to = 8)) {} Is that better, or worse ;)
Marcel Lamothe
Sorry, that's "(from: 1, to: 8)"
Marcel Lamothe
Personally, that would be too verbose and clunky-looking for me to use (as opposed to a for loop) but I understand that it's to some degree a matter of taste.
mquander
Sounds like a good case for an extension method 1.to(8)
Mark
Just use Ruby then :P
mquander
A: 

I agree that in many (or even most cases) foreach is much more readable than a standard for-loop when simply iterating over a collection. However, your choice of using Enumerable.Range(index,count) isn't a strong example of the value of foreach over for.

For a simple range starting from 1, Enumerable.Range(index, count) looks quite readable. However, if the range starts with a different index, it becomes less readable because you have to properly perform index + count - 1 to determine what the last element will be. For example...

// Write the numbers 2 thru 8
foreach( var index in Enumerable.Range( 2, 7 ) )
{
    Console.WriteLine( index );
}

In this case, I much prefer the second example.

// Write the numbers 2 thru 8
for( int index = 2; index <= 8; index++ )
{
    Console.WriteLine( index );
}
Dustin Campbell
I agree - similiar to Spender's comment on iterating from min to max.
Marcel Lamothe
+1  A: 

I think the foreach + Enumerable.Range is less error prone (you have less control and less ways to do it wrong, like decreasing the index inside the body so the loop would never end, etc.)

The readability problem is about the Range function semantics, that can change from one language to another (e.g if given just one parameter will it begin from 0 or 1, or is the end included or excluded or is the second parameter a count instead a end value).

About the performance, I think the compiler should be smart enough to optimize both loops so they execute at a similar speed, even with large ranges (I suppose that Range does not create a collection, but of course an iterator).

fortran
A: 

I do like the foreach + Enumerable.Range approach and use it sometimes.

// does anyone object to the new style over the traditional style?
foreach(var index in Enumerable.Range( 1, 7 ))

I object to the var abuse in your proposal. I appreciate var, but, damn, just write int in this case! ;-)

frou
Heh, I was wondering when I was going to get dinged for that one :)
Marcel Lamothe
I updated my example so as not to deter from the main question.
Marcel Lamothe
+5  A: 

This is just for fun. (I'd just use the standard "for (int i = 1; i <= 10; i++)" loop format myself.)

foreach (int i in 1.To(10))
{
    Console.WriteLine(i);    // 1,2,3,4,5,6,7,8,9,10
}

// ...

public static IEnumerable<int> To(this int from, int to)
{
    if (from < to)
    {
        while (from <= to)
        {
            yield return from++;
        }
    }
    else
    {
        while (from >= to)
        {
            yield return from--;
        }
    }
}

You could also add a Step extension method too:

foreach (int i in 5.To(-9).Step(2))
{
    Console.WriteLine(i);    // 5,3,1,-1,-3,-5,-7,-9
}

// ...

public static IEnumerable<T> Step<T>(this IEnumerable<T> source, int step)
{
    if (step == 0)
    {
        throw new ArgumentOutOfRangeException("step", "Param cannot be zero.");
    }

    return source.Where((x, i) => (i % step) == 0);
}
LukeH
public static IEnumerable<int> To(this int from, int to) { return Enumerable.Range(from, to); }
@unknown, Enumerable.Range can only count forwards, my version counts backwards too. Your code also produces a different sequence to mine: try "foreach (int i in 5.To(15))".
LukeH
Very good, this is the sort of thing I wanted to hear when I posted the question!
Marcel Lamothe
Interesting stuff!
Marcel Lamothe
A: 

You can actually do this in C#:

1.To(7).Do(Console.WriteLine);

SmallTalk forever!

Nice - I assume you'd just create a couple of extension methods for "To" and "Do"?
Marcel Lamothe
I thought that C++ syntax is bad LOL
Artyom
A: 

Strictly speaking, you misuse enumeration.

Enumerator provides the means to access all the objects in a container one-by-one, but it does not guarantee the order.

It is OK to use enumeration to find the biggest number in an array. If you are using it to find, say, first non-zero element, you are relying on the implementation detail you should not know about. In your example, the order seems to be important to you.

Edit: I am wrong. As Luke pointed out (see comments) it is safe to rely on the order when enumerating an array in C#. This is different from, for example, using "for in" for enumerating an array in Javascript .

buti-oxa
Not sure - I mean, the Range method is built to return an incrementing sequence, so I'm not sure how I'm relying on an implementation detail. Can you (or anyone else) elaborate?
Marcel Lamothe
I do not think it is promised that the sequence returned is incrementing. Of course, it always is in practice. You can enumerate any collection. For many, there are a lot of orders that make sense, and you may get any of them.
buti-oxa
@Marcel, @buti-oxa: The process of enumeration (for example, with foreach) doesn't guarantee any specific ordering in itself, but the ordering *is* determined by the enumerator used: The built-in enumerator for Array, List<> etc will always iterate in element order; for SortedDictionary<>, SortedList<> etc it'll always iterate in key comparison order; and for Dictionary<>, HashSet<> etc there's no guaranteed ordering. I'm pretty confident that you can rely on Enumerable.Range always iterating in ascending order.
LukeH
Sure, the enumerator knows the order it provides, so if I write the enumerator, I can rely on order. If compiler/library does is for me, how do I know. Does it say somewhere in spec/documentation that built-in enumerator for array always iterate from 1 to N? I could not find this promise.
buti-oxa
@buti-oxa, Take a look at section 8.8.4 (page 240) of the C#3 spec: "For single-dimensional arrays elements are traversed in increasing index order, starting with index 0 and ending with index Length – 1." (http://download.microsoft.com/download/3/8/8/388e7205-bc10-4226-b2a8-75351c669b09/CSharp%20Language%20Specification.doc)
LukeH
+1  A: 

I'd like to have the syntax of some other languages like Python, Haskell, etc.

// Write the numbers 1 thru 7
foreach (int index in [1..7])
{
    Console.WriteLine(index);
}

Fortunatly, we got F# now :)

As for C#, I'll have to stick with the Enumerable.Range method.

Thomas Danecker
A: 

@Luke: I reimplemented your To() extension method and used the Enumerable.Range() method to do it. This way it comes out a little shorter and uses as much infrastructure given to us by .Net as possible:

public static IEnumerable<int> To(this int from, int to)
{ 
    return from < to 
            ? Enumerable.Range(from, to - from + 1) 
            : Enumerable.Range(to, from - to + 1).Reverse();
}
Thorsten Lorenz