Pokud pracujete s poli nebo kolekcemi, jistě jste někdy použili cyklus For Each. Velmi jednoduše pomocí něj můžeme projít pole hodnot nebo třeba kolekci objektů. Celý tento cyklus funguje díky mechanismu enumerátorů.
Enumerátor je speciální třída implementující rozhraní IEnumerator, která umí procházet položky nějaké datové struktury. V tomto .NET tipu si ukážeme, jak můžeme takový enumerátor sami napsat.
Napíšeme si třídu NumberSequence, které v konstruktoru předáme tři čísla – počíteční hodnotu, koncovou hodnotu a krok, po němž se má pohybovat.
''' <summary>
''' Sekvence čísel
''' </summary>
Public Class NumberSequence
Private _startValue As Integer
''' <summary>
''' Počáteční hodnota
''' </summary>
Public Property StartValue() As Integer
Get
Return _startValue
End Get
Set(ByVal value As Integer)
_startValue = value
End Set
End Property
Private _endValue As Integer
''' <summary>
''' Koncová hodnota
''' </summary>
Public Property EndValue() As Integer
Get
Return _endValue
End Get
Set(ByVal value As Integer)
_endValue = value
End Set
End Property
Private _increment As Integer
''' <summary>
''' Přírůstek po každém kroku
''' </summary>
Public Property Increment() As Integer
Get
Return _increment
End Get
Set(ByVal value As Integer)
_increment = value
End Set
End Property
''' <summary>
''' Vytvoří novou instanci třídy NumberSequence
''' </summary>
Public Sub New(ByVal _startValue As Integer, ByVal _endValue As Integer, ByVal _increment As Integer)
Me.StartValue = _startValue
Me.EndValue = _endValue
Me.Increment = _increment
End Sub
End Class
Nyní si napíšeme náš enumerátor, je to třída implementující rozhraní IEnumerator.
''' <summary>
''' Enumerátor pro třídu NumberSequence
''' </summary>
Public Class NumberSequenceEnumerator
Implements IEnumerator
Private _parent As NumberSequence
Private _currentStep As Integer = -1
''' <summary>
''' Vytvoří novou instanci třídy
''' </summary>
Public Sub New(ByVal _parent As NumberSequence)
Me._parent = _parent
End Sub
Public ReadOnly Property Current() As Object Implements System.Collections.IEnumerator.Current
Get
'vrátit aktuální položku
Return _parent.StartValue + _parent.Increment * _currentStep
End Get
End Property
Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext
'přesunout se na další
_currentStep += 1
Return (Me.Current <= _parent.EndValue) 'pokud ještě nejsme na konci, vracet True
End Function
Public Sub Reset() Implements System.Collections.IEnumerator.Reset
'nastavit enumerátor na začátek
_currentStep = -1
End Sub
End Class
V tomto enumerátoru si pamatujeme krok, na kterém právě stojíme, v proměnné _currentStep. Je nutné vědět, že před přístupem k prvnímu prvku se volá MoveNext, takže v počátečním stavu musíme být jakoby před prvním prvkem. Do tohoto počátečního stavu nás také musí dostat volání metody Reset.
Jinak je zajímavá akorát vlastnost Current, která vrací aktuální hodnotu, a funkce MoveNext, která přejde na další prvek. Jak se tyto hodnoty spočítají, to už je věc implementace našeho enumerátoru, tady je počítám. Samozřejmě když ale budete psát lineární spojový seznam, bude logika těchto metod trochu jiná, asi si budete pamatovat vrchol, na kterém jste, a v metodě MoveNext akorát přejdete na další, to už záleží na konkrétní situaci.
Poslední věc, která nám nyní zbývá, je říct třídě NumberSequence, že když na ni použijeme cyklus For Each, že se má procházet pomocí tohoto enumerátoru. Jak na to? Naimplementujeme rozhraní IEnumerable:
''' <summary>
''' Sekvence čísel
''' </summary>
Public Class NumberSequence
Implements IEnumerable
...
''' <summary>
''' Vrátí novou instanci našeho enumerátoru
''' </summary>
Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
Return New NumberSequenceEnumerator(Me)
End Function
End Class
A to je celé. Použití je pak velmi jednoduché:
' vypsat násobky 3 do 30
For Each i As Integer In New NumberSequence(3, 30, 3)
Console.WriteLine(i)
Next
Console.ReadKey()
Tímto vypíšete na obrazovku násobky tří v rozsahu od 3 do 30. Samozřejmě v praxi je tohle lepší dělat jednoduchým For cyklem, ale cílem tohoto článku bylo ukázat způsob, jakým si můžete napsat vlastní enumerátor, tohle je jen příklad.
Díky enumerátorům budete schopni napsat si vlastní kontejnery a datové struktury, které budou podporovat snadné procházení For cyklem. Většinu používaných datových struktur má .NET ve třídách System.Collections.Generic. Jinak samozřejmě od IEnumerable a IEnumerator existují i generické varianty.