Ответ 1
Вы можете сделать то, что foreach
делает под капотом, но с двумя счетчиками:
using(var e1 = list1.GetEnumerator())
using(var e2 = list2.GetEnumerator())
{
while(e1.MoveNext() && e2.MoveNext())
{
var item1 = e1.Current;
var item2 = e2.Current;
// use item1 and item2
}
}
Для удобства вы можете написать метод расширения, например следующий, который принимает действие:
public static void ZipDo<T1, T2>( this IEnumerable<T1> first, IEnumerable<T2> second, Action<T1, T2> action)
{
using (var e1 = first.GetEnumerator())
using (var e2 = second.GetEnumerator())
{
while (e1.MoveNext() && e2.MoveNext())
{
action(e1.Current, e2.Current);
}
}
}
и используйте его как:
list1.ZipDo(list2, (i1,i2) => i1.Use(i2));
Кстати, вы можете развернуть это, чтобы использовать 3 или более списков:
public static void ZipDo<T1, T2, T3>(this IEnumerable<T1> first,
IEnumerable<T2> second, IEnumerable<T3> third,
Action<T1, T2, T3> action)
{
using (var e1 = first.GetEnumerator())
using (var e2 = second.GetEnumerator())
using (var e3 = third.GetEnumerator())
{
while (e1.MoveNext() && e2.MoveNext() && e3.MoveNext())
{
action(e1.Current, e2.Current, e3.Current);
}
}
}
Приведенный выше подход требуется, когда коллекции имеют разные общие типы. Однако, если все они имеют один и тот же общий тип, вы можете написать гибкий метод, который принимает любое число IEnumerable<T>
s:
public static void ZipAll<T>(this IEnumerable<IEnumerable<T>> all, Action<IEnumerable<T>> action)
{
var enumerators = all.Select(e => e.GetEnumerator()).ToList();
try
{
while (enumerators.All(e => e.MoveNext()))
action(enumerators.Select(e => e.Current));
}
finally
{
foreach (var e in enumerators)
e.Dispose();
}
}
и используйте его:
var lists = new[] {
new[]{ 1, 1, 1 },
new[]{ 2, 2, 2 },
new[]{ 3, 3, 3 }};
lists.ZipAll(nums => Console.WriteLine(nums.Sum()));
// 6
// 6
// 6