Per implementare l'interfaccia Ienumerable è necessario poter costruire una istanza di un IEnumerator.

È necessario quindi implementare i metodi:

- MoveNext

- Reset

- Dispose

E la proprietà Current sia nella versione generica che in quella non generica in quanto l'interfaccia generica estende quella non generica.

Per semplificare questo processo è possibile utilizzare l’istruzione "yield return". Questa funzionalità può essere usata in un metodo get o in un metodo qualunque purché il tipo di ritorno sia uno tra: IEnumerator, IEnumerable è le rispettive generiche. L’istruzione “yield break” viene terminata per segnalare la fine dell’enumerazione della successione di elementi. Nell’esempio qua sotto vediamo come gli iteratori non generici rappresentano una successione di “object” e quindi non sono type safe ma sarà necessario effettuare un opportuno cast al tipo corretto di oggetto con il rischio ovviamente di incappare in errori a runtime.

 

public class Test
{
    public IEnumerator EnumeratorNonGenerico()
    {
        yield return 1;
        yield return 2;
        yield return 4;
        yield return "test";
        yield break;
    }

    public IEnumerator<int> EnumeratorGenerico()
    {
        yield return 1;
        yield return 2;
        yield return 4;
        yield break;
    }

    public IEnumerable EnumerableNonGenerico()
    {
        yield return 1;
        yield return 2;
        yield return 4;
        yield return "test";
        yield break;
    }

    public IEnumerable<int> EnumerableGenerico()
    {
        yield return 1;
        yield return 2;
        yield return 4;
        yield break;
    }

}

Utilizzando questo costrutto stiamo dichiarando che il blocco di codice è un iteratore. Quando l'esecuzione del codice raggiunge una istruzione yield return immediatamente viene restituito il primo valore al blocco chiamante. Questo significa che non tutti i valori che potrebbe restituire il nostro metodo vengono effettivamente restituiti o addirittura generati. Per vederlo aggiungiamo un metodo alla nostra classe Test

public IEnumerable<int> EnumerableEccezione()
{
    yield return 1;
    throw new Exception("Errore!");
}

 In questo modo possiamo eseguire il seguente snippet e verificare che possiamo estrarre il primo valore e non ricevere mai l’eccezione

var t = new Test();

foreach (var item in t.EnumerableEccezione())
{
    Console.WriteLine(item);
    break;
}

 

Questo codice inizia l’iterazione, stampa il primo valore ed esce dal ciclo. Il fatto che non riceviamo nessuna eccezione significa che le righe di codice sotto il primo “yield return” non vengono mai eseguite.

Tutto questo significa che il compilatore genera per noi tutto il codice necessario alla definizione dell'iteratore, un lavoro abbastanza complicato e che impone delle restrizioni sull'uso dell'istruzione. Per i dettagli rimando alla documentazione msdn.

I metodi che compongono Linq sono in gran parte metodi che lavorano su IEnumerable<T> è restituiscono IEnumerable<T>. L'istruzione yield return è vitale per snellire il lavoro e produrre del codice semplice e chiaro ma estremamente efficace. Nel prossimo post analizzeremo  l'implementazione del metodo Where dove vedremo un esempio di uso di yield return.

comments powered by Disqus