Consider a simple foreach example (Example 1):
foreach (var item in somecollection)
{
Console.WriteLine(item)
}
Suppose I want to include the iteration count--i.e the item index--in the console message. If I maintain the use of foreach, the following is a typical pattern that results (Example 2):
{
int i = 0;
foreach (var item in somecollection)
{
Console.WriteLine($"Item {i++}: {item}"):
}
}
Not only is this pattern tedious to do correctly, there is also a tendency to leak the variable "i" to the encompassing scope, a broader context than the one in which it is needed. It requires precise attention to detail to recognize the need for the surrounding block whose sole purpose is to contain the scope of the i variable.
Sometimes this scenario can be a motivating factor in converting to the for(int i= 0 ;; ++i) syntax, but that approach becomes impractical if the collection doesn't support random access.
I propose a new compile-time keyword operator, here dubbed 'indexof', which would be used as follows (Example 3):
foreach (var item in somecollection)
{
console.writeLine($"Item {indexof(item)}: {item}" ) ;
}
This would compile to code which is equivalent to the output of Example 2. It would eliminate a few classes of bugs, and make the code more readable.
This also comes in handy for nesting scenarios (Example 4):
foreach (var item in somecollection)
{
foreach (var subItem in item.subCollection)
{
console.writeLine($"Item.Subitem {indexof(item)}.{indexof(subItem)}: {subItem}" ) ;
}
}
This example eliminates two temporary variables, and two false decouplings between counter and iterator. For example, it would be impossible to swap i and j indexes inadvertently.
I imagine it to be more challenging to implement, but it would be no less useful in Linq (Example 5):
somecollection.ToList().ForEach(_ => console.WriteLine($"Item {indexof(_)}: {_}" ));
We could enable a brazen and robust copy/paste by permitting an inferred operand syntax. In the case following, indexof() is given no parameter. Instead, it would be expected to count the number of times it enters the current scope, within the lifetime of the surrounding scope. This is essentially identical to the behavior of Example 3, illustrating that the only reason the index(item) form is needed is in the case of ambiguity. (Example 6):
foreach (var item in somecollection)
{
console.writeLine($"Item {indexof()}: {item}" ) ;
}
This would make it so that some expressions were valid in any loop, without adapting to the names of specific variables. (Copy/paste friendly, minimal redundancy).
Other names I've considered:
This is just a variant of #8130 which had a better syntax than this, but was still closed as "Won't Fix". Something like an indexof
is confusing because it implies that it knows the index of the iterated from the original array/collection which is certainly not the case given that an enumerator can enumerate in way it wishes.
As for "LINQ", that would just be an extension method which you can author yourself right now:
public static class CollectionExtensions {
public static void ForEach<T>(this ICollection<T> collection, Action<T, int> action) {
int index = 0;
foreach (T item in collection) {
action(item, index);
index += 1;
}
}
}
LINQ already supports this. Select( ( item, index ) => )
It would be nice to be able to stop writing:
var iter = collection.GetEnumerator();
for (int i = 0; iter.MoveNext(); i += 1)
{
var value = iter.Current;
}
... but adding "secret" values seems, confusing. @HaloFour suggested a very workable solution, which only needs to be added once per project and does resolve the problem.
And with value tuples, it could be taken further to something like:
public static class CollectionExtensions {
public static IEnumerator<ValueTuple<T, int>> Iterator<T>(this ICollection<T> collection) {
var iter = collection.GetEnumerator();
for (int i = 0; iter.MoveNext(); i += 1) {
var val = iter.Current;
yield return (val, i);
}
}
}
...
foreach (var item in myCollection.Iterator)
{
object value = item.Item1;
int index = item.Item2;
...
}
Some other languages like Go have tuple-based syntax for this purpose. If we import this syntax to C#, it will be like:
foreach (var (index, item) in collections) ;
However, is it enough to have an extension method with tuples as follows?
public static IEnumerable<(int index, T item)> WithIndex<T>(this IEnumerable<T> items)
=> items.Select((i, x) => (i, x));
foreach (var (index, item) in collections.WithIndex()) ;
with tuples you can already do this pretty trivially...
foreach(var (item, index) in myCollection.Select((it,i)=> (item,i)))
{
}
@ufcpp You just described the Swift approach it seems :):
http://stackoverflow.com/questions/24028421/swift-for-loop-for-index-element-in-array
I took a crack at some of the ideas mentioned with the capabilities of C# 6, with this result:
long lastOne = 0;
foreach (var nthItem in GetFibonacciSequence().Enumerated())
{
Console.WriteLine($"{nthItem.index}: {nthItem.item}");
if (nthItem.index > 91)
{
lastOne = nthItem.item;
break;
}
}
Enumerated() looks like this:
public struct EnumeratedItem<T, U>
{
public T item;
public U index;
}
public static IEnumerable<EnumeratedItem<T, int>> Enumerated<T>(this IEnumerable<T> collection)
{
int index = 0;
foreach (T item in collection)
{
yield return new EnumeratedItem<T, int>
{
index = index++,
item = item
};
}
}
I guess I don't hate this. The main problem is that it requires me to name an entity I don't want to name (nth item), and doesn't permit me to name the one entity I do want to name (the elements returned from GetFibonacciSequence(), and which I have to refer to as nthItem.Item).
It looks like the tuples support in C#7 might change this, allowing creation of an Enumerate() extension method that returns a tuple with good names of my choosing. It's better since I can now name the thing I care about, but I don't like that I still have to pick a name for the index, and that the choice is decoupled from the item itself or from the concept of a loop scope counter. It shares the namespace with other variables, so I might _have_ to pick a name for it, and while still making sure the index name stays consistent with the iterated name.
That's the problem that indexof(n) is trying to solve: it's coupled so precisely to its idea, and the relationship of that idea to the iteration variable it is used with, that you can't give it it's own name, and you'd never want to. I can rename n and change its meaning, but I can't change the meaning of indexof(n), so I want neither the flexibility nor the responsibility to name it. Name conflicts with n might be possible, be never with the derived index.
Well, after there is tuple it is as easy as
C#
foreach (var (index,item) in somecollection.Select((x,i) => (i,x)))
{
Console.WriteLine(index,item)
}
@Thaina - For the stated goal, I find that too verbose. That said, I don't see any reason your .Select(...) call couldn't be extracted into a simple .Enumerate() extension method, as I suggested in my prior post. In that post I also visited the pros/cons of the tuples syntax.
Just to note that performance of
foreach (var (index,item) in somearray.Select((x,i) => (i,x))) { ... }
does not match performance of foreach
for arrays, since array foreach gets compiled to for
and doesn't use IEnumerable
at all.
I definitely vote for some kind of index with no overhead other than one local variable.
Issue moved to dotnet/csharplang #627 via ZenHub
Moved discussion to csharplang repo.
Most helpful comment
Some other languages like Go have tuple-based syntax for this purpose. If we import this syntax to C#, it will be like:
However, is it enough to have an extension method with tuples as follows?