2009-10-20 21 views
10

kiedy mam blok koduwzór pracy zwrotu plonu

static void Main() 
{ 

    foreach (int i in YieldDemo.SupplyIntegers()) 
    { 
    Console.WriteLine("{0} is consumed by foreach iteration", i); 
    } 
} 


class YieldDemo 
    { 
    public static IEnumerable<int> SupplyIntegers() 
    { 
     yield return 1; 
     yield return 2; 
      yield return 3; 
     } 
    } 

mogę zinterpretować zasadę za powrót plonu jako

  1. Main() wywołuje SupplyIntegers()
  2. |1| |2| |3| are stored in contiguous memory block.Pointer of "IEnumerator" Moves to |1|
  3. Kontrola zwraca z SupplyInteger() na Main().
  4. Główna() drukuje wartość
  5. Wskaźnik Przejście do | 2 |, i tak dalej.

Wyjaśnienia:

(1) Normalnie mamy jeden ważny return jest dozwolone Wewnątrz function.How robi C# traktuje gdy zwrot wielokrotny plon, plon powrót ... stwierdzenia są obecne ?

(2) Po napotkaniu zwrotu nie ma możliwości, aby kontrola ponownie wróciła do SupplyIntegers(), w przypadku, gdy jest to dozwolone, czy wydajność nie zacznie się od 1? Mam na myśli zwrot z inwestycji 1?

+0

Re prośbą o książce: C# w głębi (Manning, Skeet), rozdział 6. Jest to bezpłatny przykładowy rozdział i obejmuje bloki iteratorów. Nie jest to właściwie książka dla początkujących C# (daleko od niej), ale będziesz mieć trudności ze znalezieniem lepszego odniesienia na ten temat. –

+0

Jeśli uprawnienia zezwalają na "nie napisałeś żadnych książek?" – user193276

+0

Nie, nie mam. Robię korektę dla wydawcy i czasami piszę ten dziwny artykuł itp. Ale nie mam książek. –

Odpowiedz

30

Nie - daleko od tego; Napiszę dla ciebie wersję z długą ręką ... jest zbyt grungy!


Uwaga to również pomaga, jeśli zrozumieją, że foreach jest rzeczywiście:

using(var iterator = YieldDemo.SupplyIntegers().GetEnumerator()) { 
    int i; 
    while(iterator.MoveNext()) { 
     i = iterator.Current; 
     Console.WriteLine("{0} is consumed by foreach iteration", i); 
    } 
} 

using System; 
using System.Collections; 
using System.Collections.Generic; 
static class Program 
{ 
    static void Main() 
    { 

     foreach (int i in YieldDemo.SupplyIntegers()) 
     { 
      Console.WriteLine("{0} is consumed by foreach iteration", i); 
     } 
    } 
} 

class YieldDemo 
    { 

    public static IEnumerable<int> SupplyIntegers() 
    { 
     return new YieldEnumerable(); 
     } 
    class YieldEnumerable : IEnumerable<int> 
    { 
     public IEnumerator<int> GetEnumerator() 
     { 
      return new YieldIterator(); 
     } 
     IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } 
    } 
    class YieldIterator : IEnumerator<int> 
    { 
     private int state = 0; 
     private int value; 
     public int Current { get { return value; } } 
     object IEnumerator.Current { get { return Current; } } 
     void IEnumerator.Reset() { throw new NotSupportedException(); } 
     void IDisposable.Dispose() { } 
     public bool MoveNext() 
     { 
      switch (state) 
      { 
       case 0: value = 1; state = 1; return true; 
       case 1: value = 2; state = 2; return true; 
       case 2: value = 3; state = 3; return true; 
       default: return false; 
      } 
     } 
    } 
} 

Jak widać, buduje maszynę państwową w iteracyjnej, ze stanem maszyna postępowała przez MoveNext. Użyłem wzorca z polem state, ponieważ widać, jak to działałoby w bardziej złożonych iteratorach.

ważne:

  • jakieś zmienne w swoim bloku iteratora stać pola na maszynie państwowej
  • jeśli masz blok finally (łącznie using), to idzie w Dispose()
  • częściami kod prowadzący do yield return staje się case (w przybliżeniu)
  • staje się state = -1; return false; (lub podobny)

Sposób, w jaki kompilator C# to robi, jest bardzo skomplikowany, ale sprawia, że ​​pisanie iteratorów jest proste.

+2

. Niewiarygodne, że ludzie robią to, zanim faktycznie jest to przydatna odpowiedź. – Joren

+1

@Joren: Tak jak dobry jest Marc. ;) Znakomita odpowiedź Marc! – jrista

+1

To naprawdę proste, kiedy Marc mówi, że zamierza coś opublikować, możesz założyć, że wszystko będzie dobrze. – kemiller2002

3

To tylko cukier składniowy, .net generuje dla ciebie klasę IEnumerator i implementuje metody MoveNext, Current and Reset, niż generuje IEnumarable klasy GetEnumerator, która zwraca IEnumerator, możesz zobaczyć klasy magiczne za pomocą reflektora .net lub ildasm.

zobaczyć również here

1

w skrócie, (podczas gdy rok czeka na wersji długo ręki Marca), gdy kompilator widzi wypowiedzi plon, za kulisami buduje nową instancję klasy niestandardowej dla Ciebie, który implementuje interfejs o nazwie IEnumerator, która ma metody Current() i MoveNext() i śledzi miejsce, w którym obecnie znajduje się proces iteracji ... W powyższym przykładzie, jako przykład, będzie również śledzić wartości na liście, które mają być wyliczone.

2

Po prostu, bloki iteratorów (lub metody z instrukcjami yield, jeśli możesz) są przekształcane przez kompilator w klasę generowaną przez kompilator. Ta klasa implementuje IEnumerator, a instrukcja yield przekształca się w "stan" dla tej klasy.

Na przykład w ten sposób:

yield return 1; 
yield return 2; 
yield return 3; 

może dostać przekształcona coś podobnego do:

switch (state) 
{ 
    case 0: goto LABEL_A; 
    case 1: goto LABEL_B; 
    case 2: goto LABEL_C; 
} 
LABEL_A: 
    return 1; 
LABEL_B: 
    return 2; 
LABEL_C: 
    return 3; 

Iterator bloki są mogą być postrzegane jako wydobywane maszyn stanowych. Ten kod zostanie wywołany metodami IEnumerator.

-2

Czy pamiętasz dyktowanie w szkole? Załóżmy, że mamy pięcioro dzieci, przybywając po dyktando słów angielskich. Mamy listę słów zapisanych na tablicy. Dzieci nie widzą tablicy, ale nauczyciele mogą ją zobaczyć. Należy pamiętać, że słowa należy podawać w tej samej kolejności dla wszystkich dzieci. Każde dziecko jest uczęszczane przez innego nauczyciela.

Tablica:
      Stany Zjednoczone Ameryki
      Wielka Brytania
      Indie
Pytania
a. Kim są klienci?
b. Kto jest dostawcą danych?
c. Kto dyktuje kolekcję? Iterating lub Enumerator?
Odpowiedzi:
a. Dzieci
b. Tablica
c. Nauczyciel

w C# kategoriach:

tablica - Data Provider - IEnumerable
nauczyciel - Iterator - stan maszyny
Dzieci - Client - ForEach