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