Search code examples
c#foreachienumerablec#-8.0

do something foreach Index in a Range


C# 8.0 introduced the structs System.Index and System.Range

What is the most concise way to loop through a System.Range?

var owners = new string[] {"Alice", "Bob", "Charlie"};
var pets = new string[] {"Dog", "Cat", "Bird"};

foreach (var index in 1..3) {
    var pet = pets[index];
    var owner = owners[index];
    Console.WriteLine($"{owner} owns a {pet}");
}

The above line foreach (var index in 1..3) { is a compile error.

Type 'System.Range' cannot be used in 'foreach' statement because it neither implements 'IEnumerable' or 'IEnumerable', nor has suitable 'GetEnumerator' method which return type has 'Current' property and 'MoveNext' method


Solution

  • I'm afraid you can't enumerate a System.Range(you can, see my edit) because it doesn't implement IEnumerable<int>. The reason is that it could contain indexes from the end of a collection.

    If you want that you need to use a for-loop or Enumerable.Range:

    foreach (var index in Enumerable.Range(0, 3)) {
        var pet = pets[index];
        var owner = owners[index];
        Console.WriteLine($"{owner} owns a {pet}");
    }
    

    If all you want is to get the items of two collections at the same index use Enumerable.Zip as Dmitry has shown.


    Edit: Actually you can do it with an extension(credits here, note my bugfix)

    public static class RangeEx
    {
        public static RangeEnumerator GetEnumerator(this Range range)
        {
            if (range.Start.IsFromEnd || range.End.IsFromEnd)
            {
                throw new ArgumentException(nameof(range));
            }
    
            return new RangeEnumerator(range.Start.Value, range.End.Value);
        }
    
        public struct RangeEnumerator : IEnumerator<int>
        {
            private readonly int _end;
            private int _current;
    
            public RangeEnumerator(int start, int end)
            {
                _current = start - 1; // - 1 fixes a bug in the original code
                _end = end;
            }
    
            public int Current => _current;
            object IEnumerator.Current => Current;
    
            public bool MoveNext() => ++_current < _end;
    
            public void Dispose() { }
            public void Reset()
            {
                throw new NotImplementedException();
            }
        }
    }
    

    With this you can use your code:

    foreach (int index in 1..3)
    {
        Console.WriteLine($"{owners[index]} owns a {pets[index]}");
    }