Algoritmo para implementar C declaração # rendimento
-
02-07-2019 - |
Pergunta
Eu adoraria descobrir sozinho mas eu queria saber aproximadamente o que é o algoritmo para converter uma função com declarações de rendimento em uma máquina de estado para um enumerador Por exemplo, como é que C # transformar isso?:
IEnumerator<string> strings(IEnumerable<string> args)
{ IEnumerator<string> enumerator2 = getAnotherEnumerator();
foreach(var arg in arg)
{ enumerator2.MoveNext();
yield return arg+enumerator.Current;
}
}
a este:
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;
}
Claro que o resultado pode ser completamente diferente dependendo do código original.
Solução
O exemplo de código especial que você está olhando envolve uma série de transformações. Por favor, note que esta é uma descrição aproximada do algoritmo. Os nomes reais usados ??pelo compilador e o código exato que ele gera pode ser diferente. A idéia é a mesma, no entanto.
A primeira transformação é a transformação "foreach", que transforma este código:
foreach (var x in y)
{
//body
}
para este código:
var enumerator = y.GetEnumerator();
while (enumerator.MoveNext())
{
var x = enumerator.Current;
//body
}
if (y != null)
{
enumerator.Dispose();
}
A segunda transformação encontra todas as declarações do retorno de rendimento no corpo da função, atribui um número a cada um (um valor de estado), e cria um "goto rótulo" logo após o rendimento.
A terceira transformação levanta todas as variáveis ??locais e argumentos da função no corpo do método em um objeto chamado de fechamento.
Dado o código em seu exemplo, que seria semelhante a esta:
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;
}
}
}
O corpo do método é então transferida do método original para um método dentro "Encerramento" chamada MoveNext, que retorna um bool e implementos IEnumerable.MoveNext. Qualquer acesso a qualquer moradores é encaminhado através de "presente", e qualquer acesso a membros da classe são encaminhados através this.originalThis.
Qualquer "rendimento retorno expr" é traduzido em:
currentValue = expr;
state = //the state number of the yield statement;
return true;
Qualquer declaração de rendimento ruptura é traduzido em:
state = -1;
return false;
Há uma declaração de rendimento break "implícita" no final da função. A instrução switch é então introduzida no início do processo, que olha para o número de estado e salta para o rótulo associado.
O método original é então traduzido para algo como isto:
IEnumerator<string> strings(IEnumerable<string> args)
{
return new ClosureEnumerable(this,args);
}
O fato de que o estado do método é tudo empurrado para um objeto e que o método MoveNext usa uma variável instrução switch / estado é o que permite o iterador para se comportar como se o controle está sendo passado de volta para o ponto imediatamente após a última "rendimento retorno" declaração na próxima vez "MoveNext" é chamado.
É importante salientar, no entanto, que a transformação usada pelo compilador C # não é a melhor maneira de fazer isso. Ele sofre de mau desempenho ao tentar usar "rendimento" com algoritmos recursivos. Há um bom papel que descreve uma maneira melhor de fazer isso aqui:
http://research.microsoft.com/en-us /projects/specsharp/iterators.pdf
Vale a pena ler se você não leu ainda.
Outras dicas
Apenas viu esta questão - I escreveu um artigo sobre ele recentemente. Eu vou ter que adicionar os outros links mencionados aqui para o artigo embora ...
Raymond Chen responde a esta; http://blogs.msdn.com/b/ oldnewthing / Arquivo / 2008/08/12 / 8849519.aspx
(editado para apontar para uma parte da série, que não faz parte 4)