Помогите мне понять фрагмент кода в С#
Я читаю этот блог: Паттерны труб и фильтров
Я смущен этим фрагментом кода:
public class Pipeline<T>
{
private readonly List<IOperation<T>> operations = new List<IOperation<T>>();
public Pipeline<T> Register(IOperation<T> operation)
{
operations.Add(operation);
return this;
}
public void Execute()
{
IEnumerable<T> current = new List<T>();
foreach (IOperation<T> operation in operations)
{
current = operation.Execute(current);
}
IEnumerator<T> enumerator = current.GetEnumerator();
while (enumerator.MoveNext());
}
}
Какова цель этого утверждения: while (enumerator.MoveNext());? кажется, этот код является noop.
Ответы
Ответ 1
Сначала рассмотрим это:
IEnumerable<T> current = new List<T>();
foreach (IOperation<T> operation in operations)
{
current = operation.Execute(current);
}
Этот код, как представляется, создает вложенные перечисления, каждый из которых принимает элементы из предыдущего, применяет к ним некоторую операцию и передает результат в следующий. Но он только строит перечислимые числа. На самом деле ничего не происходит. Он просто готов к работе, сохранен в переменной current
. Существует множество способов реализовать IOperation.Execute
, но это может быть что-то вроде этого.
IEnumerable<T> Execute(IEnumerable<T> ts)
{
foreach (T t in ts)
yield return this.operation(t); // Perform some operation on t.
}
Другой вариант, предложенный в статье, - это сортировка:
IEnumerable<T> Execute(IEnumerable<T> ts)
{
// Thank-you LINQ!
// This was 10 lines of non-LINQ code in the original article.
return ts.OrderBy(t => t.Foo);
}
Теперь посмотрим на это:
IEnumerator<T> enumerator = current.GetEnumerator();
while (enumerator.MoveNext());
Это фактически приводит к выполнению цепочки операций. Когда элементы запрашиваются из перечисления, они заставляют элементы из исходного перечислимого пройти через цепочку IOperations
, каждая из которых выполняет некоторую операцию над ними. Конечный результат отбрасывается, поэтому интересен только побочный эффект операции - например, запись на консоль или запись в файл. Это был бы более простой способ написать две последние строки:
foreach (T t in current) {}
Еще одна вещь, которую следует соблюдать, состоит в том, что начальный список, который запускает процесс, представляет собой пустой список, поэтому для того, чтобы это имело смысл, некоторые экземпляры T должны быть созданы внутри первой операции. В этой статье это делается путем запроса пользователя для ввода с консоли.
Ответ 2
В этом случае while (enumerator.MoveNext());
просто оценивает все элементы, возвращаемые окончательным IOperation<T>
. Это выглядит немного запутанным, но пустой List<T>
создается только для того, чтобы передать значение первому IOperation<T>
.
Во многих коллекциях это ничего не сделает, как вы предлагаете, но учитывая, что мы говорим о шаблоне труб и фильтров, вполне вероятно, что конечное значение - это своего рода итератор, который вызовет выполнение кода. Это может быть что-то вроде этого, например (предполагая, что это целое число):
public class WriteToConsoleOperation : IOperation<int>
{
public IEnumerable<int> Execute(IEnumerable<int> ints)
{
foreach (var i in ints)
{
Console.WriteLine(i);
yield return i;
}
}
}
Таким образом, вызов MoveNext()
для каждого элемента в IEnumerator<int>
, возвращаемого этим итератором, вернет каждое из значений (которые игнорируются в цикле while
), но также выводит каждое из значений на консоль.
Это имеет смысл?
Ответ 3
while (enumerator.MoveNext());
Внутри текущего блока кода нет никакого влияния (он перемещается по всем элементам перечисления). Отображаемый код не действует на текущий элемент в перечислении. Что может случиться, так это то, что метод MoveNext() перемещается к следующему элементу и делает что-то с объектами в коллекции (обновляет внутреннее значение, вытаскивает следующий из базы данных и т.д.). Поскольку тип List<T>
, это, вероятно, не так, но в других случаях это может быть.