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.

Foi útil?

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)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top