可以通过foreach向后迭代吗?


129

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


6
您可以先对列表中的元素(list.Reverse())进行反转,然后再对每个元素进行反转。
Akanthan 2013年

1
另请参阅“ 为什么没有
反向枚举器”

您将必须实例化枚举中的所有元素,以便可以颠倒它们的顺序。这可能是不可能的。考虑一下顺序:IEnumerable<int> Infinity() { int i = 1; while (true) yield return i++; }您将如何逆转?
Suncat2000

Answers:


84

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

编辑:这通常意味着,当您能够使用for循环时,它可能是此任务的正确方法。另外,就foreach顺序实现而言,构造本身是为了表示独立于元素索引和迭代顺序的循环而构建的,这在并行编程中尤其重要。这是我的观点是迭代依托为了不应该使用foreach的循环。


3
我发现你的最后声明有些笼统。当然,在某些情况下,例如,需要依次迭代某种IEnumerable?在这种情况下,您不会使用foreach吗?你会用什么?
avl_sweden

1
更新:请参阅[Bryan对类似问题的答案[(stackoverflow.com/a/3320924/199364),以使用Linq并讨论列表和其他枚举,以获得更现代的答案。
ToolmakerSteve

更新:乔恩·斯基特(Jon Skeet)给出了一个更为优雅的答案,现在可以使用“ yield return”来实现。
制造商史蒂夫(Steve)

2
我的初始反应与@avl_sweden相同-“依赖顺序的迭代不应使用foreach”听起来过于宽泛。是的,foreach可以很好地表达与订单无关的并行任务。但它也是现代基于迭代器的编程的重要组成部分。也许关键是,如果有两个不同的关键字来阐明一个是否断言迭代是与顺序无关的,那将更清晰/更安全?[给定一种代码,鉴于像埃菲尔这样的可以传播合同的语言,这种断言可以证明是对还是错。]
ToolmakerSteve

2
foreach“是为表达独立于元素索引和迭代顺序的循环而构建”的想法是错误的。C#语言规范要求按顺序包含foreach过程元素。通过在迭代器上使用MoveNext或通过处理从零开始并在每次数组迭代中增加一的索引来处理。
唐纳德·里奇

145

如果您使用的是.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传递给堆栈构造函数时,它将迭代它,并将项目推入堆栈。然后,当您遍历堆栈时,它将以相反的顺序弹出。

Reverse()如果为它提供一个IEnumerable且永不停止返回项目,则该方法和.NET 3.5 扩展方法显然会崩溃。


我也忘了只提.net v2
JL。

4
有趣的.NET 2.0解决方案。
RichardOD

11
我是否缺少某些东西,或者您的.Net 3.5解决方案实际上不起作用?Reverse()会在原处反转列表,但不返回它。太糟糕了,因为我希望有这样的解决方案。
2012年

2
一些管理员将此答案标记为“错误”。Reverse()是void [1],因此上面的示例导致编译错误。[1] msdn.microsoft.com/en-us/library/b0axc2h2(v=vs.110).aspx
MickyD 2014年

6
@ user12861,Micky Duncan:答案没有错,您丢失了一些东西。System.Collections.Generic.List <T>上有一个Reverse方法,该方法执行就地反向。在.Net 3.5中,IEnumerable <T>上有一个名为Reverse的扩展方法。我已将示例从var更改为IEnumerable <int>,以使其更加明确。
Matt Howells 2014年

55

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

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()缓冲所有数据快。(我不相信这样做Reverse会有任何优化Count()。)请注意,这种缓冲意味着第一次开始迭代时会完全读取数据,而FastReverse将“看到”对列表所做的任何更改。(如果在两次迭代之间删除多个项目,它也会中断。)

对于一般序列,无法进行反向迭代-序列可以是无限的,例如:

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

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


出于好奇,为什么使用“> = 0”而不是“> -1”?
克里斯S

15
>为什么使用“> = 0”而不是“> -1”?因为> = 0可以更好地将意图传达给人类阅读代码。如果这样做可以提高性能,则编译器应该能够将其优化为等效值> -1。
Mark Maslar

1
FastReverse(此IList <T>项)应为FastReverse <T>(此IList <T>项。):
Rob

分割棒0 <= i甚至更好。多年来,在教了很多孩子数学知识并帮助人们进行编码之后,我发现它可以减少人们总是将a> b换成b <a时人们所犯的错误,因为事情以自然的顺序排列数(或坐标系的X轴)-唯一的缺点是,如果您需要将等式粘贴到XML中,例如.config
Eske Rahn

13

foreach用于迭代之前,请通过以下reverse方法反转列表:

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


谨慎行事myList会有所帮助。IEnumerable.Reverse在这里不起作用。
nawfal

2
@ MA-Maddin没有两者是不同的。我猜答者依赖于List <T> .Reverse到位。
nawfal

其实我不知道我为什么这么说。我应该添加更多详细信息...无论如何,这会使代码令人困惑,因为它myList似乎是类型System.Collections.Generic.List<System.Windows.Documents.List>(或任何其他自定义List类型),否则此代码将不起作用:P
Martin Schneider

5

有时,您可能没有足够的索引编制能力,或者您想要撤消Linq查询的结果,或者您可能不想修改源集合,如果其中任何一个都是正确的,Linq可以为您提供帮助。

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

    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/zh-CN/library/vstudio/bb358497(v=vs.100).aspx


4

如果使用List <T>,则还可以使用以下代码:

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

3

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

创建一个为您完成此工作的Iterator,例如,通过IEnumerable接口执行以下实现(假设“ items”是此示例中的List字段):

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

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

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

只是一个提示:您应该在文档中清楚说明列表的这种特殊行为(甚至还可以通过选择不言自明的类名(例如Stack或Queue更好)。



1

乔恩·斯凯特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];
}

然后用作

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

-1

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

                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) {

2
这很糟糕,因为.Reverse()实际上是在修改列表。
科林·巴斯内特

-8

这个效果很好

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

9
这对我来说听起来效率极低。
Jean Azzopardi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.