Алгоритм реализации оператора доходности C#
-
02-07-2019 - |
Вопрос
Я бы хотел разобраться в этом сам, но мне было интересно примерно каков алгоритм преобразования функции с операторами доходности в конечный автомат для перечислителя? Например, как C# это поворачивает:
IEnumerator<string> strings(IEnumerable<string> args)
{ IEnumerator<string> enumerator2 = getAnotherEnumerator();
foreach(var arg in arg)
{ enumerator2.MoveNext();
yield return arg+enumerator.Current;
}
}
в это:
bool MoveNext()
{ switch (this.state)
{
case 0:
this.state = -1;
this.enumerator2 = getAnotherEnumerator();
this.argsEnumerator = this.args.GetEnumerator();
this.state = 1;
while (this.argsEnumerator.MoveNext())
{
this.arg = this.argsEnumerator.Current;
this.enumerator2.MoveNext();
this.current = this.arg + this.enumerator2.Current;
this.state = 2;
return true;
state1:
this.state = 1;
}
this.state = -1;
if (this.argsEnumerator != null) this.argsEnumerator.Dispose();
break;
case 2:
goto state1;
}
return false;
}
Конечно, результат может быть совершенно разным в зависимости от исходного кода.
Решение
Конкретный пример кода, который вы рассматриваете, включает в себя ряд преобразований.Обратите внимание, что это приблизительное описание алгоритма.Фактические имена, используемые компилятором, и точный код, который он генерирует, могут отличаться.Однако идея та же самая.
Первое преобразование — это преобразование foreach, которое преобразует этот код:
foreach (var x in y)
{
//body
}
в этот код:
var enumerator = y.GetEnumerator();
while (enumerator.MoveNext())
{
var x = enumerator.Current;
//body
}
if (y != null)
{
enumerator.Dispose();
}
Второе преобразование находит все операторы return return в теле функции, присваивает каждому номер (значение состояния) и создает «метку перехода» сразу после выхода.
Третье преобразование переносит все локальные переменные и аргументы функции в теле метода в объект, называемый замыканием.
Учитывая код в вашем примере, это будет выглядеть примерно так:
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, который возвращает логическое значение и реализует IEnumerable.MoveNext.Любой доступ к любым локальным объектам маршрутизируется через this, а любой доступ к любым членам класса маршрутизируется через this.originalThis.
Любое «выражение доходности и возврата» переводится в:
currentValue = expr;
state = //the state number of the yield statement;
return true;
Любое заявление о разрыве доходности преобразуется в:
state = -1;
return false;
В конце функции есть «неявный» оператор выхода из строя.Затем в начале процедуры вводится оператор переключения, который просматривает номер состояния и переходит к соответствующей метке.
Исходный метод затем переводится примерно так:
IEnumerator<string> strings(IEnumerable<string> args)
{
return new ClosureEnumerable(this,args);
}
Тот факт, что все состояние метода помещается в объект и что метод MoveNext использует оператор переключения/переменную состояния, позволяет итератору вести себя так, как будто управление передается обратно в точку сразу после последнего «возврата доходности». " при следующем вызове "MoveNext".
Однако важно отметить, что преобразование, используемое компилятором C#, — не лучший способ сделать это.Он страдает от низкой производительности при попытке использовать «выход» с рекурсивными алгоритмами.Есть хороший документ, в котором описывается лучший способ сделать это:
http://research.microsoft.com/en-us/projects/specsharp/iterators.pdf
Стоит прочитать, если вы еще не читали.
Другие советы
Только заметил этот вопрос - я написал статью об этом недавно.Однако мне придется добавить в статью другие ссылки, упомянутые здесь...
На этот вопрос отвечает Раймонд Чен; http://blogs.msdn.com/b/oldnewthing/archive/2008/08/12/8849519.aspx
(отредактировано, чтобы указать на часть 1 серии, а не на часть 4)