可以通过 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使用列表(直接索引)时,您无法像使用 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<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"? > 为什么使用 ">= 0" 而不是 "> -1"?因为 >= 0 可以更好地将意图传达给阅读代码的人。如果这样做可以提高性能,编译器应该能够将其优化为等效的 > -1。 FastReverse(this IList在使用foreach
进行迭代之前,通过reverse
方法反转列表:
myList.Reverse();
foreach( List listItem in myList)
Console.WriteLine(listItem);
【讨论】:
同Matt Howells' answer from '09 请注意myList
的含义。 IEnumerable.Reverse 在这里不起作用。
@MA-Maddin 不,两者不同。我猜回答者依赖于 ListmyList
似乎是System.Collections.Generic.List<System.Windows.Documents.List>
类型(或任何其他自定义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 向后迭代吗?的主要内容,如果未能解决你的问题,请参考以下文章
在 .NET 中,使用“foreach”迭代 IEnumerable<ValueType> 的实例会创建一个副本吗?那么我应该更喜欢使用“for”而不是“foreach”吗?