【问题标题】:Possible to iterate backwards through a foreach?可以通过 foreach 向后迭代吗?
【发布时间】:2010-11-15 17:53:29
【问题描述】:

我知道我可以使用 for 语句并达到相同的效果,但我可以通过 C# 中的 foreach 循环向后循环吗?

【问题讨论】:

  • 你可以在对每个元素做之前反转列表中的元素(list.Reverse())。
  • 您必须实例化枚举中的所有元素,以便您可以颠倒它们的顺序。这可能是不可能的。考虑一下这个顺序:IEnumerable<int> Infinity() { int i = 1; while (true) yield return i++; }你会如何扭转它?

标签: c# foreach


【解决方案1】:

如果您使用的是 .NET 3.5,您可以这样做:

IEnumerable<int> enumerableThing = ...;
foreach (var x in enumerableThing.Reverse())

这不是很有效,因为它基本上必须通过枚举器将所有内容放在堆栈上,然后以相反的顺序将所有内容弹出。

如果您有一个可直接索引的集合(例如 IList),则绝对应该使用 for 循环。

如果您使用的是 .NET 2.0 并且不能使用 for 循环(即您只有一个 IEnumerable),那么您只需编写自己的 Reverse 函数。这应该有效:

static IEnumerable<T> Reverse<T>(IEnumerable<T> input)
{
    return new Stack<T>(input);
}

这依赖于一些可能不那么明显的行为。当您将 IEnumerable 传递给堆栈构造函数时,它将遍历它并将项目推入堆栈。然后,当您遍历堆栈时,它会以相反的顺序将内容弹出。

如果你给它一个永不停止返回项目的 IEnumerable,这个和 .NET 3.5 Reverse() 扩展方法显然会崩溃。

【讨论】:

  • 另外我忘了提及.net v2
  • 有趣的 .NET 2.0 解决方案。
  • 是我遗漏了什么还是您的 .Net 3.5 解决方案实际上不起作用? Reverse() 将列表反转到位并且不返回它。太糟糕了,因为我希望有这样的解决方案。
  • 请一些管理员将此答案标记为错误。 Reverse() 是一个 void[1] ,因此上面的示例会导致编译错误。 [1]msdn.microsoft.com/en-us/library/b0axc2h2(v=vs.110).aspx
  • @user12861,Micky Duncan:答案没有错,你错过了一些东西。 System.Collections.Generic.List 上有一个 Reverse 方法,它执行就地反转。在 .Net 3.5 中有一个名为 Reverse 的 IEnumerable 扩展方法。我已将示例从 var 更改为 IEnumerable 以使其更加明确。
【解决方案2】:

使用列表(直接索引)时,您无法像使用 for 循环那样高效。

编辑:这通常意味着,当您能够使用for 循环时,这可能是此任务的正确方法。另外,由于foreach 是按顺序实现的,构造本身是为表达独立于元素索引和迭代顺序的循环而构建的,这在parallel programming 中尤为重要。 我认为依赖于顺序的迭代不应该使用foreach 进行循环。

【讨论】:

  • 我觉得你的最后一句话有点太笼统了。当然,在某些情况下,例如,某种类型的 IEnumerable 需要按顺序迭代?在那种情况下你不会使用 foreach 吗?你会用什么?
  • 更新:请参阅 [Bryan 对类似问题的回答[(stackoverflow.com/a/3320924/199364),以获得更现代的答案,使用 Linq,并讨论这两个列表和其他可枚举项。
  • 更新:Jon Skeet 有一个更优雅的答案,现在可以使用“yield return”。
  • 我的最初反应与@avl_sweden 相同——“依赖于顺序的迭代不应使用 foreach”听起来过于宽泛。是的,foreach 很适合表达与顺序无关的并行任务。但它也是现代iterator-based programming 的重要组成部分。也许关键是如果有两个不同的关键字会更清晰/更安全,以澄清一个是否断言迭代是顺序无关的? [给定一种像 Eiffel 这样可以传播合同的语言,对于给定的代码,这样的断言可以证明是真或假。]
  • foreach“是为表达独立于元素索引和迭代顺序的循环而构建”的想法是不正确的。 c# 语言规范要求 foreach 流程元素按顺序排列。通过在迭代器上使用 MoveNext 或通过处理从零开始并在数组的每次迭代中递增一的索引。
【解决方案3】:

正如 280Z28 所说,对于 IList&lt;T&gt;,您可以只使用索引。您可以将其隐藏在扩展方法中:

public static IEnumerable<T> FastReverse<T>(this IList<T> items)
{
    for (int i = items.Count-1; i >= 0; i--)
    {
        yield return items[i];
    }
}

这将比Enumerable.Reverse() 更快,后者首先缓冲所有数据。 (我不相信ReverseCount() 那样应用了任何优化。)请注意,这种缓冲意味着数据在您第一次开始迭代时被完全读取,而FastReverse 将“看到”任何更改在您迭代时添加到列表中。 (如果您在迭代之间删除多个项目,它也会中断。)

对于一般序列,没有办法反向迭代 - 序列可能是无限的,例如:

public static IEnumerable<T> GetStringsOfIncreasingSize()
{
    string ret = "";
    while (true)
    {
        yield return ret;
        ret = ret + "x";
    }
}

如果您尝试反向迭代,您会期望发生什么?

【讨论】:

  • 只是好奇,为什么使用 ">= 0" 而不是 "> -1"?
  • > 为什么使用 ">= 0" 而不是 "> -1"?因为 >= 0 可以更好地将意图传达给阅读代码的人。如果这样做可以提高性能,编译器应该能够将其优化为等效的 > -1。
  • FastReverse(this IList items) 应该是 FastReverse(this IList items.:)
  • 分割棒 0b 换成 b
【解决方案4】:

在使用foreach 进行迭代之前,通过reverse 方法反转列表:

    myList.Reverse();
    foreach( List listItem in myList)
    {
       Console.WriteLine(listItem);
    }

【讨论】:

  • 请注意myList 的含义。 IEnumerable.Reverse 在这里不起作用。
  • @MA-Maddin 不,两者不同。我猜回答者依赖于 List.Reverse 。
  • 其实我也不知道为什么这么说。我应该添加更多细节...无论如何这是令人困惑的代码,因为myList 似乎是System.Collections.Generic.List&lt;System.Windows.Documents.List&gt; 类型(或任何其他自定义List 类型),否则此代码不起作用:P
【解决方案5】:

如果你使用 List,你也可以使用这个代码:

List<string> list = new List<string>();
list.Add("1");
list.Add("2");
list.Add("3");
list.Reverse();

这是一种将列表反向写入的方法。

现在是 foreach:

foreach(string s in list)
{
    Console.WriteLine(s);
}

输出是:

3
2
1

【讨论】:

    【解决方案6】:

    有时您没有索引的奢侈,或者您可能想要反转 Linq 查询的结果,或者您可能不想修改源集合,如果这些都是真的,Linq 可以提供帮助你。

    使用匿名类型和 Linq Select 的 Linq 扩展方法为 Linq OrderByDescending 提供排序键;

        public static IEnumerable<T> Invert<T>(this IEnumerable<T> source)
        {
            var transform = source.Select(
                (o, i) => new
                {
                    Index = i,
                    Object = o
                });
    
            return transform.OrderByDescending(o => o.Index)
                            .Select(o => o.Object);
        }
    

    用法:

        var eable = new[]{ "a", "b", "c" };
    
        foreach(var o in eable.Invert())
        {
            Console.WriteLine(o);
        }
    
        // "c", "b", "a"
    

    之所以命名为“Invert”,是因为它是“Reverse”的同义词,并且可以消除 List Reverse 实现的歧义。

    也可以反转集合的某些范围,因为 Int32.MinValue 和 Int32.MaxValue 超出了任何集合索引的范围,我们可以将它们用于排序过程;如果元素索引低于给定范围,则为其分配 Int32.MaxValue,以便在使用 OrderByDescending 时其顺序不会改变,类似地,索引大于给定范围的元素将分配 Int32.MinValue,以便它们出现在订购过程的末尾。给定范围内的所有元素都被分配了它们的正常索引并相应地反转。

        public static IEnumerable<T> Invert<T>(this IEnumerable<T> source, int index, int count)
        {
            var transform = source.Select(
                (o, i) => new
                {
                    Index = i < index ? Int32.MaxValue : i >= index + count ? Int32.MinValue : i,
                    Object = o
                });
    
            return transform.OrderByDescending(o => o.Index)
                            .Select(o => o.Object);
        }
    

    用法:

        var eable = new[]{ "a", "b", "c", "d" };
    
        foreach(var o in eable.Invert(1, 2))
        {
            Console.WriteLine(o);
        }
    
        // "a", "c", "b", "d"
    

    我不确定这些 Linq 实现与使用临时 List 包装集合以进行逆向相比对性能的影响。


    在撰写本文时,我还不知道 Linq 自己的 Reverse 实现,不过,解决这个问题很有趣。 https://msdn.microsoft.com/en-us/library/vstudio/bb358497(v=vs.100).aspx

    【讨论】:

      【解决方案7】:

      如果您可以更改实现 IEnumerable 或 IEnumerable(例如您自己的 IList 实现)的集合代码是可能的。

      创建一个Iterator 为您完成这项工作,例如通过IEnumerable 接口实现以下实现(假设'items' 是此示例中的一个列表字段):

      public IEnumerator<TObject> GetEnumerator()
      {
          for (var i = items.Count - 1; i >= 0; i--)
          { 
              yield return items[i];
          }
      }
      
      IEnumerator IEnumerable.GetEnumerator()
      {
          return GetEnumerator();
      }
      

      因此,您的 List 将以相反的顺序遍历您的列表。

      只是一个提示:你应该在文档中清楚地说明你的列表的这种特殊行为(最好选择一个自我解释的类名,比如 Stack 或 Queue)。

      【讨论】:

        【解决方案8】:

        Jon Skeet 的好回答稍加阐述,这可能是多才多艺的:

        public static IEnumerable<T> Directional<T>(this IList<T> items, bool Forwards) {
            if (Forwards) foreach (T item in items) yield return item;
            else for (int i = items.Count-1; 0<=i; i--) yield return items[i];
        }
        

        然后用 as

        foreach (var item in myList.Directional(forwardsCondition)) {
            .
            .
        }
        

        【讨论】:

          【解决方案9】:

          没有。 ForEach 只是遍历每个项目的集合,顺序取决于它是使用 IEnumerable 还是 GetEnumerator()。

          【讨论】:

          • 嗯,有顺序保证,具体取决于集合类型。
          【解决方案10】:

          我为列表类型添加了一个扩展,以帮助我懒惰地反向循环列表

          public static IEnumerable<T> LazyReverse<T>(this IList<T> items)
          {
              for (var i = items.Count - 1; i >= 0; i--)
                  yield return items[i];
          }
          

          这样使用

          foreach(var item in list.LazyReverse()){
            // do some work .....
            if(item == somecondition){
               break;// break without loading the reset of the list
            }
          }
          

          【讨论】:

            【解决方案11】:

            我已经使用了这个有效的代码

                            if (element.HasAttributes) {
            
                                foreach(var attr in element.Attributes().Reverse())
                                {
            
                                    if (depth > 1)
                                    {
                                        elements_upper_hierarchy_text = "";
                                        foreach (var ancest  in element.Ancestors().Reverse())
                                        {
                                            elements_upper_hierarchy_text += ancest.Name + "_";
                                        }// foreach(var ancest  in element.Ancestors())
            
                                    }//if (depth > 1)
                                    xml_taglist_report += " " + depth  + " " + elements_upper_hierarchy_text+ element.Name + "_" + attr.Name +"(" + attr.Name +")" + "   =   " + attr.Value + "\r\n";
                                }// foreach(var attr in element.Attributes().Reverse())
            
                            }// if (element.HasAttributes) {
            

            【讨论】:

            • 这很糟糕,因为.Reverse() 实际上正在修改列表。
            【解决方案12】:

            这很好用

            List<string> list = new List<string>();
            
            list.Add("Hello");
            list.Add("Who");
            list.Add("Are");
            list.Add("You");
            
            foreach (String s in list)
            {
                Console.WriteLine(list[list.Count - list.IndexOf(s) - 1]);
            }
            

            【讨论】:

            • 这听起来对我来说非常低效。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-09-25
            • 2011-01-03
            • 1970-01-01
            • 2010-09-16
            • 2010-12-05
            相关资源
            最近更新 更多