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