如何同时迭代两个IEnumerables?

时间:2010-04-27 14:17:51

标签: c# iteration ienumerable

我有两个枚举:IEnumerable<A> list1IEnumerable<B> list2。我想同时迭代它们,如:

foreach((a, b) in (list1, list2))
{
    // use a and b
}

如果它们不包含相同数量的元素,则应抛出异常。

这样做的最佳方式是什么?

8 个答案:

答案 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>

上使用.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
}
然而,它不会抛出不等长度。不过,你总是可以测试它。

答案 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
}

这不会引发异常,但是......