Il For Each loop e le interfacce IEnumerable e IEnumerator
pubblicato il 07/01/2009
La prima cosa che mi ha colpito della versione 2.0 del framework, ed in
particolare del nuovo VB, è stata la possibilità di creare classi Generic, il
cui tipo cioè venga determinato solo al momento dell'uso, pur mantenendo i
vantaggi della tipizzazione forte. L'uso che viene spontaneo è quello delle
collection, sulle quali possiamo definire le nostre proprietà e metodi di
estensione. Fatto ciò volevo fare in modo che la mia collection potesse essere
scorsa con un bel loop For Each; documentazione alla mano si tratta
di implementare l'interfaccia IEnumerable e definire l'unico metodo
GetEnumerator. Semplice, no? Quasi, perchè a questo punto il
diavolo ci ha messo la coda...
Innanzitutto, la documentazione che stavo leggendo si riferiva
all'implementazione dell'interfaccia IEnumerable normale,
non Generic. Al che le mie uniche due sinapsi ripresesi dai bagordi festivi hanno
cominciato una subdola opera di persuasione per convincermi che io dovevo assolutamente
implementare l'interfaccia Generic.IEnumerable.
In secondo luogo, l'interfaccia IEnumerable da sola, non basta mica! Pretende
a tutti i costi di essere accompagnata dalla sua cara sorella, IEnumerator.
Anche in questo caso le due sinapsi si sono messe di buzzo buono per confondermi
le idee, con la versione Generics.IEnumerator.
Credo che la cosa più semplice ed immediata sia presentare il codice (molto scheletrico, beninteso) e commentarlo:
Public Class GenericCollection(Of T)
' La classe Generic per la quale realizzare la scansione tramite For Each...
' A scanso di equivoci, qui non presento i metodi aggiutivi rispetto
' alla Generics.ArrayList standard
Implements IEnumerable
' Prima interfaccia da implementare: espone il metodo GetEnumerator
Implements IEnumerator
' Seconda interfaccia da implementare: espone i metodi Reset e MoveNext
' e la proprietà Current
Private m_Position As Integer = -1
' La "posizione" del cursore all'interno della collection.
' N.B.: DEVE essere inizializzata al valore -1: se non venisse inizializzata
' assumerebbe il valore di default 0 (ZERO) che provocherebbe un malfunzionamento
Private m_Values As New ArrayList
' L'arrayList dei valori
Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
' Non c'è molto da dire, se non che deve essere implementata così com'è scritta....
Return CType(Me, IEnumerator)
End Function
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
' Su questa è meglio spendere due paroline: la MoveNext viene richiamata
' all'inizializzazione dell'enumeratore e la prima cosa che fa è incrementare
' la posizione del cursore nella Collection (o ArrayList, in questo caso).
' Questo spiega perchè è così importante inizializzare correttamente
' la variabile m_Position: lasciando il default = 0 la prima chiamata
' al metodo MoveNext imposterebbe la variabile a 1: l'effetto finale
' sarebbe che il ciclo For Each "salterebbe" il primo elemento (avente
' indice = 0)!
m_Position += 1
Return (m_Position > m_ValueTable.Count)
End Function
Public Sub Reset() Implements IEnumerator.Reset
m_Position = -1
End Sub
Public ReadOnly Property Current() As Object Implements IEnumerator.Current
Get
' In questo caso è sufficente un cast dell'elemento nella collection
' al tipo T
Return CType(m_Values.Item(m_Position), T)
End Get
End Property
End Class
Adesso che è semplice (e funziona....)
