如何同时迭代两个IEnumerables?
我必须枚举IEnumerable<A> list1
和IEnumerable<B> list2
并且想要同时遍历它们,如:
foreach((a, b) in (list1, list2)) { // use a and b }
如果它们不包含相同数量的元素,则应抛出exception。
做这个的最好方式是什么?
下面是这个操作的实现,通常称为Zip:
using System; using System.Collections.Generic; namespace SO2721939 { public sealed class ZipEntry<T1, T2> { public ZipEntry(int index, T1 value1, T2 value2) { Index = index; Value1 = value1; Value2 = value2; } public int Index { get; private set; } public T1 Value1 { get; private set; } public T2 Value2 { get; private set; } } public static class EnumerableExtensions { public static IEnumerable<ZipEntry<T1, T2>> Zip<T1, T2>( this IEnumerable<T1> collection1, IEnumerable<T2> collection2) { if (collection1 == null) throw new ArgumentNullException("collection1"); if (collection2 == null) throw new ArgumentNullException("collection2"); int index = 0; using (IEnumerator<T1> enumerator1 = collection1.GetEnumerator()) using (IEnumerator<T2> enumerator2 = collection2.GetEnumerator()) { while (enumerator1.MoveNext() && enumerator2.MoveNext()) { yield return new ZipEntry<T1, T2>( index, enumerator1.Current, enumerator2.Current); index++; } } } } class Program { static void Main(string[] args) { int[] numbers = new[] { 1, 2, 3, 4, 5 }; string[] names = new[] { "Bob", "Alice", "Mark", "John", "Mary" }; foreach (var entry in numbers.Zip(names)) { Console.Out.WriteLine(entry.Index + ": " + entry.Value1 + "-" + entry.Value2); } } } }
如果只有其中一个序列的值超出范围,就会引发exception,请更改while循环:
while (true) { bool hasNext1 = enumerator1.MoveNext(); bool hasNext2 = enumerator2.MoveNext(); if (hasNext1 != hasNext2) throw new InvalidOperationException("One of the collections ran " + "out of values before the other"); if (!hasNext1) break; yield return new ZipEntry<T1, T2>( index, enumerator1.Current, enumerator2.Current); index++; }
你想要的东西像Zip
LINQ操作符 – 但是当.NET 4中的任何一个序列结束时,它们总是会被截断。
MoreLINQ实现有一个EquiZip
方法,它将引发一个InvalidOperationException
。
var zipped = list1.EquiZip(list2, (a, b) => new { a, b }); foreach (var element in zipped) { // use element.a and element.b }
总之,这种语言没有提供干净的方式来做到这一点。 枚举被devise为一次完成一个枚举。 你可以很容易地模仿foreach对你的影响:
IEnumerator<A> list1enum = list1.GetEnumerator(); IEnumerator<B> list2enum = list2.GetEnumerator(); while(list1enum.MoveNext() && list2enum.MoveNext()) { // list1enum.Current and list2enum.Current point to each current item }
如果长度不同,该怎么办取决于你。 也许在while循环完成后找出哪个元素仍然有元素,并继续使用该元素,如果它们的长度相同,则抛出exception。
在.NET 4中,可以在IEnumerable<T>
上使用.Zip扩展方法
IEnumerable<int> list1 = Enumerable.Range(0, 100); IEnumerable<int> list2 = Enumerable.Range(100, 100); foreach (var item in list1.Zip(list2, (a, b) => new { a, b })) { // use item.a and item.b }
但是,这不会造成不相等的长度。 不过你可以随时testing。
去IEnumerable.GetEnumerator,所以你可以移动的枚举。 请注意,这可能有一些非常讨厌的行为,你一定要小心。 如果你想得到它的工作,去这个,如果你想有可维护的代码,使用两个foreach。
你可以创build一个包装类或使用一个库(如Jon Skeet所build议的)以更通用的方式处理这个function,如果你要通过你的代码多次使用它。
我build议的代码:
var firstEnum = aIEnumerable.GetEnumerator(); var secondEnum = bIEnumerable.GetEnumerator(); var firstEnumMoreItems = firstEnum.MoveNext(); var secondEnumMoreItems = secondEnum.MoveNext(); while (firstEnumMoreItems && secondEnumMoreItems) { // Do whatever. firstEnumMoreItems = firstEnum.MoveNext(); secondEnumMoreItems = secondEnum.MoveNext(); } if (firstEnumMoreItems || secondEnumMoreItems) { Throw new Exception("One Enum is bigger"); } // IEnumerator does not have a Dispose method, but IEnumerator<T> has. if (firstEnum is IDisposable) { ((IDisposable)firstEnum).Dispose(); } if (secondEnum is IDisposable) { ((IDisposable)secondEnum).Dispose(); }
using(var enum1 = list1.GetEnumerator()) using(var enum2 = list2.GetEnumerator()) { while(true) { bool moveNext1 = enum1.MoveNext(); bool moveNext2 = enum2.MoveNext(); if (moveNext1 != moveNext2) throw new InvalidOperationException(); if (!moveNext1) break; var a = enum1.Current; var b = enum2.Current; // use a and b } }
你可以做这样的事情。
IEnumerator enuma = a.GetEnumerator(); IEnumerator enumb = b.GetEnumerator(); while (enuma.MoveNext() && enumb.MoveNext()) { string vala = enuma.Current as string; string valb = enumb.Current as string; }
C#没有任何foreach可以做你想要的(我知道的)。
像使用Zip
函数
foreach (var entry in list1.Zip(list2, (a,b)=>new {First=a, Second=b}) { // use entry.First und entry.Second }
这不会抛出exception,但…