Home > Software engineering >  do something foreach Index in a Range
do something foreach Index in a Range

Time:12-05

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

CodePudding user response:

If you want to enumerate (owner, pet) tuples you can do it with a help of Linq Zip instead of Range:

using System.Linq;

...

foreach (var (owner, pet) in owners.Zip(pets, (o, p) => (o, p))) {
  Console.WriteLine($"{owner} owns a {pet}");
}

If you want to Skip the very 1st owner (Alice) and her pet (Dog) just add Skip (in your code you have range starting from 1, not from 0):

foreach (var (owner, pet) in owners.Zip(pets, (o, p) => (o, p)).Skip(1)) {
  Console.WriteLine($"{owner} owns a {pet}");
}

CodePudding user response:

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]}");
}

CodePudding user response:

You can't use foreach loop with Range since it doesn't implement IEnumerable interface. You can only use Range if you want to show a part of array for example

Range range = 0..2;

var i=range.Start.Value;
foreach (var owner in owners[range])
{
    Console.WriteLine($"{owner} owns a {pets[i]}");
    i  ;
}

result

Alice owns a Dog
Bob owns a Cat

or

Range range = 1..3;

var i=range.Start.Value;
foreach (var owner in owners[range])
{
    Console.WriteLine($"{owner} owns a {pets[i]}");
    i  ;
}

result

Bob owns a Cat
Charlie owns a Bird

but in your case it is much easier to use for loop

for (var i=0; i <3; i  ) Console.WriteLine($"{owners[i]} owns a {pets[i]}");
  • Related