Наиболее важное соображение разделяет логику от представления - меньше связи будет делать будущие изменения в обоих намного более простыми.
Вы могли бы даже хотеть рассмотреть использование своего рода системы шаблонной обработки как присяжный острослов .
Nope - far from it; I'll write a long-hand version for you... it is too grungy!
Note it also helps if you understand that the foreach
is actually:
using(var iterator = YieldDemo.SupplyIntegers().GetEnumerator()) {
int i;
while(iterator.MoveNext()) {
i = iterator.Current;
Console.WriteLine("{0} is consumed by foreach iteration", i);
}
}
using System;
using System.Collections;
using System.Collections.Generic;
static class Program
{
static void Main()
{
foreach (int i in YieldDemo.SupplyIntegers())
{
Console.WriteLine("{0} is consumed by foreach iteration", i);
}
}
}
class YieldDemo
{
public static IEnumerable<int> SupplyIntegers()
{
return new YieldEnumerable();
}
class YieldEnumerable : IEnumerable<int>
{
public IEnumerator<int> GetEnumerator()
{
return new YieldIterator();
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
class YieldIterator : IEnumerator<int>
{
private int state = 0;
private int value;
public int Current { get { return value; } }
object IEnumerator.Current { get { return Current; } }
void IEnumerator.Reset() { throw new NotSupportedException(); }
void IDisposable.Dispose() { }
public bool MoveNext()
{
switch (state)
{
case 0: value = 1; state = 1; return true;
case 1: value = 2; state = 2; return true;
case 2: value = 3; state = 3; return true;
default: return false;
}
}
}
}
As you can see, it builds a state machine in the iterator, with the state machine progressed by MoveNext
. I've used the pattern with a state
field, as you can see how this would work for more complex iterators.
Importantly:
finally
block (including using
), it goes in the Dispose()
yield return
become a case
(roughly)yield break
becomes a state = -1; return false;
(or similar)The way the C# compiler does this is very complicated, but it makes writing iterators a breeze.
короче (пока вы ждете длинную версию от Марка), когда компилятор видит операторы yield, за кулисами он создает для вас новый экземпляр настраиваемого класса, который реализует интерфейс, называемый IEnumerator
, который имеет методы Current ()
и MoveNext ()
, и отслеживает, где вы в данный момент находитесь в процессе итерации ... В случае выше в качестве вашего примера, он также будет отслеживать значения в списке для перечисления.
Это просто синтаксический сахар, .net генерирует для вас класс IEnumerator и реализует методы MoveNext, Current и Reset, а затем генерирует класс IEnumarable GetEnumerator, из которого возвращается этот IEnumerator, вы можете увидеть эти волшебные классы с помощью .net отражатель или ildasm.
Также см. здесь
Проще говоря, блоки итераторов (или методы с операторами yield
, если можно) преобразуются компилятором в класс, созданный компилятором. Этот класс реализует IEnumerator
, а оператор yield
преобразуется в «состояние» для этого класса.
Например, это:
yield return 1;
yield return 2;
yield return 3;
может быть преобразовано во что-то похожее на:
switch (state)
{
case 0: goto LABEL_A;
case 1: goto LABEL_B;
case 2: goto LABEL_C;
}
LABEL_A:
return 1;
LABEL_B:
return 2;
LABEL_C:
return 3;
Блоки итераторов можно рассматривать как абстрактные машины состояний. Этот код будет вызываться методами IEnumerator
.