Iteratory

Iteratory pozwalają w łatwy sposób wyspecyfikować jak instrukcja foreach ma poruszać się (iterować) po elementach pewnego typu (pewnej kolekcji). Iterator udostępnia uporządkowaną sekwencję wartości.

Aby umożliwić iterację po elementach danej klasy należy implementować w niej interfejs IEnumerable lub IEnumerable<T> (z przestrzeni nazw System.Collections lub System.Collections.Generic), a co za tym idzie także metodę GetEnumerator tego interfejsu.

Wewnątrz funkcji implementującej iterację zawieramy deklarację yeld return (zwraca kolejny element kolekcji) lub yeld break (wskazuje, że iteracja dobiegła końca).

Przykład :

class Stack : IEnumerable
{
   int[ ] table;
   ...
   public IEnumerator  GetEnumerator()
   {
      for (int i = 0; i < 10; i++)
      {
         yield return table[i];
      }
      yield break;	                     // Deklaracja opcjonalna
   }
}

Możemy także tworzyć różne iteratory dla tej samej klasy (np. iterujące po elementach w innej kolejności) - w tym takie, które pobierają parametry. Przykładowo, gdyby dodać do klasy stosu dodatkowy iterator:

public IEnumerable  FromToBy(int from, int to, int by)
{
   for (int i = from; i <= to; i += by)
   {
      yield return table[i];
   }
}
można by iterować po wybranych elementach stosu zapisując:
foreach( int i in stos.FromToBy(10,20,2 ) ) {...}

Enumerowana kolekcja powinna działać jako fabryka enumeratorów. Jeśli jest poprawnie zaimplementowana to zwracane enumeratory są od siebie niezależne.

Szablony iteratorów

Ponieważ szablony iteratorów wywodzą się ze zwykłych interfejsów, nie będących szablonami, podczas ich implementacji należy zapewnić także obsługę standardowej metody GetEnumerator - pochodzącej z interfejsu IEnumerable (nie będącego szablonem).

Przykład :
public class Stos<T> : IEnumerable<T>             // Stos jest kolekcją po której
{                                                 // można iterować np. za
   T[]  elementy;                                 // pomocą   foreach.
   int ilość;
   ...
   IEnumerable<T> IEnumerable<T>. GetEnumerator( ) {           // Poza implementacją
      for( int i=ilość - 1; i >= 0; i-- ) {                    // samej metody iteratora
         yield return elementy[i];                             // z IEnumerable<T> ...
      }
   }

   IEnumerator IEnumerable.GetEnumerator( )                     // ... konieczna jest
   {                                                            // implementacja metody
      return ((IEnumerable<T>)this).GetEnumerator();            // iteratora z IEnumerable
   }
}
...
Stos<int>  stos = new Stos<int>( );       // Deklaracja stosu
...
foreach( int i in stos) Console.Write("{0}", i);      // Przykład iteracji po elementach stosu.

Możliwość stosowania szablonów interfejsów IEnumerator<T> oraz IEnumerable<T> niesie za sobą korzyści związane z wydajnością. Podczas gdy używamy interfejsu IEnumerator, operującego na klasie object, iteracja powoduje narzut związany z rzutowaniem obiektu do właściwego typu przechowywanego w kolekcji. Dzięki zastosowaniu szablonów możemy iterować po kolekcji bez potrzeby rzutowania. Dlatego też zalecane jest stosowanie szablonów iteratorów także w stosunku do zwykłych klas, nie będących szablonami. Przykładowo:

public class CityCollection : IEnumerable<string>
{
   	string[] m_Cities = {"New York","Paris","London"};

 	IEnumerator<string> IEnumerable<string>.GetEnumerator()
   	{
   	   for(int i = 0; i<m_Cities.Length; i++)
   	      yield return m_Cities[i];
   	}

   	IEnumerator IEnumerable.GetEnumerator()
   	{
   	   return  ((IEnumerable <string>)this)GetEnumerator();
   	}
}


prev | Strona Główna | next

Copyright © 2006 Daniel Dusiński
Wszelkie prawa zastrzeżone