我有两个可枚举:
IEnumerable<A> list1
和IEnumerable<B> list2
。我想同时迭代它们,例如:
foreach((a, b) in (list1, list2))
{
// use a and b
}
如果它们不包含相同数量的元素,则应抛出异常。
最好的方法是什么?
这是此操作的实现,通常称为 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);
}
}
}
}
要使其在其中一个序列用完值时抛出异常,请更改 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++;
}
简而言之,该语言没有提供干净的方法来做到这一点。枚举被设计为一次对一个枚举进行。您可以轻松模仿 foreach 为您所做的事情:
using(IEnumerator<A> list1enum = list1.GetEnumerator())
using(IEnumerator<B> list2enum = list2.GetEnumerator())
while(list1enum.MoveNext() && list2enum.MoveNext()) {
// list1enum.Current and list2enum.Current point to each current item
}
如果它们的长度不同该怎么办取决于您。也许在 while 循环完成后找出哪一个仍然有元素并继续处理那个元素,如果它们应该具有相同的长度则抛出异常,等等。
在 .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
}
但是,它不会抛出不等的长度。不过,您始终可以测试一下。
使用 IEnumerable.GetEnumerator,这样您就可以移动可枚举值。请注意,这可能会产生一些非常令人讨厌的行为,您必须小心。如果你想让它工作,就用这个,如果你想要有可维护的代码,请使用两个 foreach。
如果您要在代码中多次使用此功能,您可以创建一个包装类或使用库(如 Jon Skeet 建议的那样)以更通用的方式处理此功能。
我建议的代码:
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
}
}
使用
Zip
功能,如
foreach (var entry in list1.Zip(list2, (a,b)=>new {First=a, Second=b}) {
// use entry.First und entry.Second
}
不过,这不会引发异常......
你可以做这样的事情。
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 可以按照你想要的方式执行(据我所知)。
自从 C# 7.0 引入元组以来,您可以创建一个返回
Lockstep
的通用 IEnumerable<(T1, T2)>
函数:
public static IEnumerable<(T1, T2)> Lockstep<T1, T2>(IEnumerable<T1> t1s, IEnumerable<T2> t2s)
{
using IEnumerator<T1> enum1 = t1s.GetEnumerator();
using IEnumerator<T2> enum2 = t2s.GetEnumerator();
while (enum1.MoveNext() && enum2.MoveNext())
yield return (enum1.Current, enum2.Current);
}
并像这样使用它:
void LockstepDemo(IEnumerable<A> xs, IEnumerable<B> ys)
{
foreach (var (x, y) in Lockstep(xs, ys))
Consume(x, y);
}
如果需要三个或更多枚举,可以使用三个或更多参数重载
Lockstep
。