Custom Iterator
Quando abbiamo a che fare con insiemi o collezioni di elementi siamo abituati ad iterare su ciascun elemento dell' insieme andando magari a compiere delle azioni guidate dalla nostra logica applicativa; ad esempio supponiamo di voler ricercare all' interno delle community .NET italiane i nominativi dei membri appartenenti alla community di XE.NET che hanno pubblicato almeno un articolo con un numero di voti superiori ad una certa soglia; potremmo scrivere un codice del tipo:
Riflettiamo un attimo sugli step eseguiti e sugli eventuali problemi che un codice del genere può portare. Sicuramente tra tutti i membri appartenenti alle varie community .NET quelli che appartengono alla comunità di XE.NET saranno una frazione relativamente piccola ma comunque consistente; creando una lista locale xeMembers il Garbage Collector rilascerà le risorse allocate dalla lista solamente quando questa sarà stata processata per intero. Con l' utilizzo dei Custom Iterator possiamo avere maggiore controllo sul flusso di iterazione definibile per le nostre collezioni.
Cominciamo analizzando il pattern Iterator evidenziando come ogni volta che si ricorre all' utilizzo del costrutto foreach il compilatore si aspetta che l'espressione fornita risulti da un'implementazione dell' interfaccia IEnumerable oppure dell' equivalente generica IEnumerable<T>; le interfacce IEnumerable<T> ed IEnumerable sono cosi definite:
1: public interface IEnumerable<T> : IEnumerable
2: {
3: IEnumerator<T> GetEnumerator();
4: }
5: public interface IEnumerable
6: {
7: IEnumerator GetEnumerator();
8: }
Queste due interfacce espongono quindi un enumeratore che permette di scorrere l'insieme del tipo per il quale viene specificato. Il compilatore quando si utilizza il costrutto foreach genera quindi del codice che utilizza l' implementazione fornita del metodo GetEnumerator(); definisce inoltre un ciclo che tiene traccia dell' elemento corrente, che viene restuituito al corpo del foreach, gestendone l' avanzamento tramite il metodo IEnumerator.MoveNext(). L' approccio che comunemente veniva adottato qualora fosse stato necessario fornire un meccanismo di iterazione prevedeva quindi la definizione di una classe apposita (solitamente nested) con il compito di fornire gli elementi al chiamante; segue un possibile esempio di tale implementazione:
Sebbene questo codice non sia eccessivamente prolisso, manca di flessibilità, in quanto non è possibile definire su di esso un comportamento parametrico in modo tale che il chiamante, possa scorrere gli elementi secondo una propria logica (ad esempio in ordine inverso).
Ecco che a questo punto ci vengono in ausilio i generatori introdotti in C# 2.0. Per definizione un generatore è una categoria speciale di iteratore in cui l'oggetto contenitore non è realizzato pienamente. Ciò permette di elaborare un elemento alla volta di collezioni astratte o addirittura infinite. I generatori sono comuni nei linguaggi di programmazione funzionali, o nei linguaggi che prendono in prestito alcuni concetti funzionali. I generatori sono spesso implementati in termini di continuazioni.
L' implementazione di un generatore in C# 2.0 prevede l'esecuzione di due passi principali:
Fatto ciò sara carico del compilatore tradurre il codice in una classe simile a quella definita in precedenza rendendo possibile per il chiamante utilizzare su di essa il costrutto foreach. Dietro le quinte questo è possibile in quanto dopo che ogni valore viene resituito (con l'istruzione yield return), lo stato della funzione viene congelato. Quando la funzione viene invocata nuovamente, l'esecuzione riprende dal punto in cui l'istruzione yield return l'aveva abbandonata, con tutte le variabili della funzione nello stato in cui si trovavano.
Tornando all' esempio mostrato ad inizio articolo vediamo come possiamo sfruttare le potenzialità dei Custom Iterator (basati sui generatori) per aumentarne flessibilità e performance adottando nell' affrontare il problema una strategia alternativa. Analizziamo il seguente codice:
Adesso gli step logici con cui eseguiamo la ricerca dei nominativi finale risulta modificata in quanto:
-
Viene letto un membro appartenente ad una community .NET
-
Viene verificata l'appartenenza alla community XE.NET
-
Viene letto un articolo appartenente al membro sopra ottenuto
-
Verifica del numero di voti ottenuti dall'articolo sotto esame
-
In caso che il numero di voti superi una certa soglia aggiunta del nomitivo dell' autore dell' articolo alla lista finale dei nominativi
Con questa nuova strategia, basata sull'utilizzo di Custom Iterator, sia per recuperare l' elenco dei membri appartenenti alle varie community .NET che per scorrerne l'elenco degli articoli pubblicati, possiamo evidenziare come in memoria siano presenti contemporaneamente solamente un' istanza di un membro della community ed un'istanza di un articolo: questo nuovo approccio permette sia una gestione della memoria più efficiente che la possibilità di scorrere gli elementi in modo custom.
indietro