【问题标题】:do something foreach Index in a Range为范围内的索引做某事
【发布时间】:2022-01-10 10:53:55
【问题描述】:

C# 8.0 引入了结构体System.IndexSystem.Range

循环遍历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}");
}

上面的foreach (var index in 1..3) {行是编译错误。

类型“System.Range”不能在“foreach”语句中使用,因为它既没有实现“IEnumerable”或“IEnumerable”,也没有合适的“GetEnumerator”方法,其返回类型具有“Current”属性和“MoveNext”方法

【问题讨论】:

  • 值得注意的是,这种特殊的编码结构被称为反模式"parallel collections"。考虑重构以使用面向对象编程。
  • 顺便说一句,使用两个数组只是为了展示一个用例。它可以传达“仅使用 for 循环”不是解决方案。

标签: c# foreach ienumerable c#-8.0


【解决方案1】:

恐怕你不能枚举System.Range(你可以,看我的编辑)因为它没有实现IEnumerable<int>。原因是它可能包含来自集合末尾的索引。

如果需要,您需要使用for-loop 或Enumerable.Range

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

如果您只想在同一索引处获取两个集合的项目,请使用 Enumerable.Zip,如 Dmitry 所示。


编辑:实际上你可以通过扩展来做到这一点(学分@​​987654322@,注意我的错误修复)

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();
        }
    }
}

这样你就可以使用你的代码了:

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

【讨论】:

    【解决方案2】:

    如果您想枚举 (owner, pet) 元组,您可以借助 Linq Zip 而不是 Range

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

    如果您想Skip 第一个所有者 (Alice) 和她的宠物 (Dog),只需添加 Skip(在您的代码中,您的范围从 1 开始,而不是从 0 开始):

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

    【讨论】:

    • 为了澄清,我真的想循环System.Range,因为我正在使用的代码库已经使用了很多范围和索引。 Linq 可以做到这一点吗?
    • @Troy:如果您必须使用 System.Range,您必须使用扩展方法,请查看 Tim Schmelter 的 答案。
    【解决方案3】:

    您不能将 foreach 循环与 Range 一起使用,因为它没有实现 IEnumerable 接口。例如,如果要显示数组的一部分,则只能使用 Range

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

    结果

    Alice owns a Dog
    Bob owns a Cat
    

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

    结果

    Bob owns a Cat
    Charlie owns a Bird
    

    但在您的情况下,使用 for 循环要容易得多

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-19
      • 2012-03-19
      • 1970-01-01
      • 2021-09-14
      • 2018-05-11
      • 2020-06-25
      相关资源
      最近更新 更多