我有两个枚举:IEnumerable<A> list1
和IEnumerable<B> list2
。我想同时迭代它们,如:
foreach((a, b) in (list1, list2))
{
// use a and b
}
如果它们不包含相同数量的元素,则应抛出异常。
这样做的最佳方式是什么?
答案 0 :(得分:49)
你想要像Zip
LINQ运算符这样的东西 - 但是当任一序列完成时,.NET 4中的版本总是会截断。
MoreLINQ implementation有一个EquiZip
方法会改为InvalidOperationException
。
var zipped = list1.EquiZip(list2, (a, b) => new { a, b });
foreach (var element in zipped)
{
// use element.a and element.b
}
答案 1 :(得分:32)
以下是此操作的实现,通常称为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++;
}
答案 2 :(得分:17)
简而言之,该语言没有提供干净的方法。枚举的目的是一次完成一个可枚举。你可以很容易地模仿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循环完成后找出哪个仍然有元素并继续使用那个元素,如果它们的长度相同则抛出异常等等。
答案 3 :(得分:3)
使用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(); }
答案 4 :(得分:3)
在.NET 4中,您可以在IEnumerable<T>
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
}
然而,它不会抛出不等长度。不过,你总是可以测试它。
答案 5 :(得分:2)
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
}
}
答案 6 :(得分:1)
你可以这样做。
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可以按照你想要的方式(我知道)。
答案 7 :(得分:1)
使用Zip
功能,如
foreach (var entry in list1.Zip(list2, (a,b)=>new {First=a, Second=b}) {
// use entry.First und entry.Second
}
这不会引发异常,但是......