2010-05-03 4 views

risposta

13

l'implementazione del rendimento non raggiunge il codice finché non è necessario.

Ad esempio, questo codice:

public IEnumerable<int> GetInts() 
{ 
    yield return 1; 
    yield return 2; 
    yield return 3; 
} 

effettivamente compilare in una classe nidificata che implementa IEnumerable<int> e il corpo di GetInts() restituirà un'istanza di tale classe.

Utilizzando riflettore è possibile vedere:

public IEnumerable<int> GetInts() 
{ 
    <GetInts>d__6d d__d = new <GetInts>d__6d(-2); 
    d__d.<>4__this = this; 
    return d__d; 
} 

Modifica - l'aggiunta più informazioni GetInts implementazione:
Il modo in cui questa implementazione rende pigri si basa sul metodo di EnumeratorMoveNext(). Quando la classe nidificata enumerabile viene generata (<GetInts>d__6d nell'esempio), ha uno stato e ad ogni stato è collegato un valore (questo è un caso semplice, nei casi più avanzati il ​​valore verrà valutato quando il codice raggiunge lo stato). Se diamo uno sguardo nel codice MoveNext() di <GetInts>d__6d vedremo lo stato:

private bool MoveNext() 
{ 
    switch (this.<>1__state) 
    { 
     case 0: 
      this.<>1__state = -1; 
      this.<>2__current = 1; 
      this.<>1__state = 1; 
      return true; 

     case 1: 
      this.<>1__state = -1; 
      this.<>2__current = 2; 
      this.<>1__state = 2; 
      return true; 

     case 2: 
      this.<>1__state = -1; 
      this.<>2__current = 3; 
      this.<>1__state = 3; 
      return true; 

     case 3: 
      this.<>1__state = -1; 
      break; 
    } 
    return false; 
} 

Quando l'enumeratore viene chiesto per l'oggetto corrente che restituisce l'oggetto, che è collegato allo stato attuale.

Al fine di dimostrare che il codice viene valutato solo in caso di necessità si può guardare a questo esempio:

[TestFixture] 
public class YieldExample 
{ 
    private int flag = 0; 
    public IEnumerable<int> GetInts() 
    { 
     yield return 1; 
     flag = 1; 
     yield return 2; 
     flag = 2; 
     yield return 3; 
     flag = 3; 
    } 

    [Test] 
    public void Test() 
    { 
     int expectedFlag = 0; 
     foreach (var i in GetInts()) 
     { 
      Assert.That(flag, Is.EqualTo(expectedFlag)); 
      expectedFlag++; 
     } 

     Assert.That(flag, Is.EqualTo(expectedFlag)); 
    } 
} 

Spero che sia un po 'più chiaro. Consiglio di dare un'occhiata al codice con Reflector e osservare il codice compilato mentre modifichi il codice "yield".

+0

@Elisha: fornire ulteriori dettagli su GetInts(). –

+0

@masoud ramezani, ha aggiunto ulteriori informazioni sulla classe di enumerazione nidificata di GetInts. – Elisha

+0

Grazie per la risposta completa. –

4

Fondamentalmente iteratori implementated utilizzando yield dichiarazioni vengono compilate in una classe che implementa un state machine.

Se non si utilizza mai foreach (= itera su e effettivamente utilizzato) il codice restituito IEnumerable<T>, il codice non viene mai effettivamente eseguito. E se lo fai, viene eseguito solo il codice minimo necessario per determinare il valore successivo da restituire, solo per riprendere l'esecuzione quando viene richiesto un valore successivo.

Si può effettivamente vedere questo comportamento accadere quando si esegue un passo singolo tale codice nel debugger. Provalo almeno una volta: penso che sia illuminante vederlo accadere passo dopo passo.