В вашем исходном коде есть неочевидная псевдо-ошибка - IEnumerator<T>
расширяет IDisposable
, поэтому вы должны ее устранить. Это может быть очень важно для блоков итераторов! Не проблема для массивов, но будет с другими IEnumerable<T>
реализациями.
Я бы сделал это так:
public static IEnumerable<TResult> PairUp<TFirst,TSecond,TResult>
(this IEnumerable<TFirst> source, IEnumerable<TSecond> secondSequence,
Func<TFirst,TSecond,TResult> projection)
{
using (IEnumerator<TSecond> secondIter = secondSequence.GetEnumerator())
{
foreach (TFirst first in source)
{
if (!secondIter.MoveNext())
{
throw new ArgumentException
("First sequence longer than second");
}
yield return projection(first, secondIter.Current);
}
if (secondIter.MoveNext())
{
throw new ArgumentException
("Second sequence longer than first");
}
}
}
Затем вы можете использовать это всякий раз, когда у вас есть необходимость:
foreach (var pair in columnList.PairUp(currentRow.Split(separatorChar),
(column, value) => new { column, value })
{
// Do something
}
Кроме того, вы можете создать общий тип Pair и избавиться от параметра проекции в методе PairUp.
EDIT:
При использовании типа Pair код вызова будет выглядеть следующим образом:
foreach (var pair in columnList.PairUp(currentRow.Split(separatorChar))
{
// column = pair.First, value = pair.Second
}
Это выглядит так просто, как вы можете получить. Да, вам нужно поместить метод утилиты где-нибудь, как код многократного использования. Вряд ли проблема на мой взгляд. Теперь для нескольких массивов ...
Если массивы разных типов, у нас проблема. Вы не можете выразить произвольное количество параметров типа в объявлении универсального метода / типа - вы можете написать версии PairUp для любого количества параметров типа, как вы хотели, точно так же, как есть делегаты Action
и Func
для максимум 4 делегировать параметры - но вы не можете сделать это произвольным.
Однако, если все значения будут одного типа - и если вы будете рады использовать массивы - это легко. (С массивами тоже все в порядке, но вы не можете выполнить проверку длины раньше времени.) Вы можете сделать это:
public static IEnumerable<T[]> Zip<T>(params T[][] sources)
{
// (Insert error checking code here for null or empty sources parameter)
int length = sources[0].Length;
if (!sources.All(array => array.Length == length))
{
throw new ArgumentException("Arrays must all be of the same length");
}
for (int i=0; i < length; i++)
{
// Could do this bit with LINQ if you wanted
T[] result = new T[sources.Length];
for (int j=0; j < result.Length; j++)
{
result[j] = sources[j][i];
}
yield return result;
}
}
Тогда код вызова будет:
foreach (var array in Zip(columns, row, whatevers))
{
// column = array[0]
// value = array[1]
// whatever = array[2]
}
Конечно, это требует определенного количества копий - вы каждый раз создаете массив. Вы можете изменить это, введя другой тип, подобный этому:
public struct Snapshot<T>
{
readonly T[][] sources;
readonly int index;
public Snapshot(T[][] sources, int index)
{
this.sources = sources;
this.index = index;
}
public T this[int element]
{
return sources[element][index];
}
}
Хотя, вероятно, большинство из них считают это излишним;)
Я мог бы продолжать выдвигать всевозможные идеи, если честно ... но основы:
- Немного поработав, вы можете сделать код вызова приятнее
- Для произвольных комбинаций типов вам придется выполнять каждое количество параметров (2, 3, 4 ...) отдельно, в связи с тем, как работают дженерики
- Если вы счастливы использовать один и тот же тип для каждой части, вы можете сделать лучше