可以通过 foreach 向后迭代吗?

Posted

技术标签:

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

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

【问题讨论】:

你可以在对每个元素做之前反转列表中的元素(list.Reverse())。 另见why-is-there-no-reverseenumerator-in-c 您必须实例化枚举中的所有元素,以便您可以颠倒它们的顺序。这可能是不可能的。考虑一下这个顺序:IEnumerable<int> Infinity() int i = 1; while (true) yield return i++; 你会如何扭转它? 【参考方案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 对类似问题的回答[(***.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);
    

【讨论】:

同Matt Howells' answer from '09 请注意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]);

【讨论】:

这听起来对我来说非常低效。

以上是关于可以通过 foreach 向后迭代吗?的主要内容,如果未能解决你的问题,请参考以下文章

我可以用foreach遍历两个大小相等的循环吗? [复制]

Swiftui foreach 内部参数

在 C# 中迭代​​字典

何时使用 forEach(_:) 而不是 for in?

foreach与for的区别

在 .NET 中,使用“foreach”迭代 IEnumerable<ValueType> 的实例会创建一个副本吗?那么我应该更喜欢使用“for”而不是“foreach”吗?