Ответ 1
Конкретный пример кода, который вы просматриваете, включает в себя ряд преобразований. Обратите внимание, что это приблизительное описание алгоритма. Фактические имена, используемые компилятором, и точный код, который он генерирует, могут отличаться. Идея та же, однако.
Первое преобразование - это преобразование "foreach", которое преобразует этот код:
foreach (var x in y)
{
//body
}
в этот код:
var enumerator = y.GetEnumerator();
while (enumerator.MoveNext())
{
var x = enumerator.Current;
//body
}
if (y != null)
{
enumerator.Dispose();
}
Второе преобразование находит все операторы yield return в теле функции, присваивает номер каждому (значение состояния) и создает "метку перехода" сразу после yield.
Третье преобразование поднимает все локальные переменные и аргументы функции в теле метода в объект, называемый замыканием.
Учитывая код в вашем примере, это будет выглядеть примерно так:
class ClosureEnumerable : IEnumerable<string>
{
private IEnumerable<string> args;
private ClassType originalThis;
public ClosureEnumerator(ClassType origThis, IEnumerable<string> args)
{
this.args = args;
this.origianlThis = origThis;
}
public IEnumerator<string> GetEnumerator()
{
return new Closure(origThis, args);
}
}
class Closure : IEnumerator<string>
{
public Closure(ClassType originalThis, IEnumerable<string> args)
{
state = 0;
this.args = args;
this.originalThis = originalThis;
}
private IEnumerable<string> args;
private IEnumerator<string> enumerator2;
private IEnumerator<string> argEnumerator;
//- Here ClassType is the type of the object that contained the method
// This may be optimized away if the method does not access any
// class members
private ClassType originalThis;
//This holds the state value.
private int state;
//The current value to return
private string currentValue;
public string Current
{
get
{
return currentValue;
}
}
}
Тело метода затем перемещается из исходного метода в метод внутри "Closure" с именем MoveNext, который возвращает bool и реализует IEnumerable.MoveNext. Любой доступ к любым местным жителям направляется через "this", а любой доступ к любым членам класса - через this.originalThis.
Любой "expr return yield" переводится на:
currentValue = expr;
state = //the state number of the yield statement;
return true;
Любой отчет о доходности переводится на:
state = -1;
return false;
В конце функции есть "неявный" оператор yield break. Затем в начале процедуры вводится оператор switch, который просматривает номер состояния и переходит к соответствующей метке.
Исходный метод затем переводится в нечто вроде этого:
IEnumerator<string> strings(IEnumerable<string> args)
{
return new ClosureEnumerable(this,args);
}
Тот факт, что состояние метода все помещается в объект и что метод MoveNext использует оператор switch/переменную состояния, позволяет итератору вести себя так, как будто управление передается обратно в точку сразу после последнего "возвращаемого результата". msgstr "оператор в следующий раз вызывается MoveNext.
Однако важно отметить, что преобразование, используемое компилятором С#, не лучший способ сделать это. Он страдает от низкой производительности при попытке использовать "yield" с рекурсивными алгоритмами. Есть хорошая статья, которая описывает лучший способ сделать это здесь:
http://research.microsoft.com/en-us/projects/specsharp/iterators.pdf
Это стоит прочитать, если вы еще не читали.