Comment parcourir deux IEnumerables simultanément?

J’ai deux énumérables: IEnumerable list1 et IEnumerable list2 . Je voudrais les parcourir simultanément comme:

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

S’ils ne contiennent pas le même nombre d’éléments, une exception doit être levée.

Quelle est la meilleure façon de procéder?

Voici une implémentation de cette opération, généralement appelée Zip:

 using System; using System.Collections.Generic; namespace SO2721939 { public sealed class ZipEntry { 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> Zip( this IEnumerable collection1, IEnumerable collection2) { if (collection1 == null) throw new ArgumentNullException("collection1"); if (collection2 == null) throw new ArgumentNullException("collection2"); int index = 0; using (IEnumerator enumerator1 = collection1.GetEnumerator()) using (IEnumerator enumerator2 = collection2.GetEnumerator()) { while (enumerator1.MoveNext() && enumerator2.MoveNext()) { yield return new ZipEntry( index, enumerator1.Current, enumerator2.Current); index++; } } } } class Program { static void Main(ssortingng[] args) { int[] numbers = new[] { 1, 2, 3, 4, 5 }; ssortingng[] names = new[] { "Bob", "Alice", "Mark", "John", "Mary" }; foreach (var entry in numbers.Zip(names)) { Console.Out.WriteLine(entry.Index + ": " + entry.Value1 + "-" + entry.Value2); } } } } 

Pour le faire lancer une exception si une seule des séquences est à court de valeurs, changez la boucle while pour:

 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( index, enumerator1.Current, enumerator2.Current); index++; } 

Vous voulez quelque chose comme l’opérateur Zip LINQ – mais la version dans .NET 4 tronque toujours simplement lorsque l’une ou l’autre séquence se termine.

L’ implémentation MoreLINQ a une méthode EquiZip qui lancera une InvalidOperationException place.

 var zipped = list1.EquiZip(list2, (a, b) => new { a, b }); foreach (var element in zipped) { // use element.a and element.b } 

En bref, la langue n’offre pas une manière propre de le faire. Le dénombrement a été conçu pour être effectué sur un énumérable à la fois. Vous pouvez facilement imiter ce que foreach fait pour vous:

 using(IEnumerator list1enum = list1.GetEnumerator()) using(IEnumerator list2enum = list2.GetEnumerator()) while(list1enum.MoveNext() && list2enum.MoveNext()) { // list1enum.Current and list2enum.Current point to each current item } 

Que faire si elles sont de longueur différente? Peut-être découvrirez-vous celui qui a encore des éléments après la boucle while et continuez à travailler avec celui-ci, lancez une exception s’ils doivent avoir la même longueur, etc.

Dans .NET 4, vous pouvez utiliser la méthode d’extension .Zip sur IEnumerable

 IEnumerable list1 = Enumerable.Range(0, 100); IEnumerable list2 = Enumerable.Range(100, 100); foreach (var item in list1.Zip(list2, (a, b) => new { a, b })) { // use item.a and item.b } 

Il ne lancera pas sur des longueurs inégales, cependant. Vous pouvez toujours tester cela, cependant.

Aller avec IEnumerable.GetEnumerator, afin que vous puissiez vous déplacer dans l’énumérable. Notez que cela peut avoir un comportement vraiment désagréable, et vous devez faire attention. Si vous voulez que cela fonctionne, allez-y, si vous voulez avoir du code maintenable, utilisez deux foreach.

Vous pouvez créer une classe d’encapsulation ou utiliser une bibliothèque (comme le suggère Jon Skeet) pour gérer cette fonctionnalité de manière plus générique si vous souhaitez l’utiliser plus d’une fois à travers votre code.

Le code pour ce que je propose:

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

Vous pouvez faire quelque chose comme ça.

 IEnumerator enuma = a.GetEnumerator(); IEnumerator enumb = b.GetEnumerator(); while (enuma.MoveNext() && enumb.MoveNext()) { ssortingng vala = enuma.Current as ssortingng; ssortingng valb = enumb.Current as ssortingng; } 

C # n’a aucun foreach qui peut le faire comme vous voulez (que je connais).

Utilisez la fonction Zip comme

 foreach (var entry in list1.Zip(list2, (a,b)=>new {First=a, Second=b}) { // use entry.First und entry.Second } 

Cela ne lance pas d’exception, cependant …