Exceptions: data structures and runtime support in virtual machines .

Nel Common Language Runtime, quando un metodo ha terminato la sua esecuzione ritorna e lo stack su cui era stato pushato l'ambiente necessario alla sua esecuzione viene abbassato nuovamente. Non c'è pertanto nessun modo tramite il quale le variabili locali a un metodo possono preservare il loro stato e rimanere visibili dopo che un metodo ha ritornato. Il costrutto yield, però, fa sì che il metodo ritorni ogni volta, ma continuando successivamente l'esecuzione e mantenendo le variabili locali con l'identico valore che avevano al momento dello yield return.

Analizziamo in che modo si comporta il compilatore per mappare questo costrutto. L'idea è quella di trasformare il metodo che implementa lo yield in una classe. Tale classe presenta i seguenti aspetti:

– le variabili locali al metodo iniziale diventano le variabili d'istanza della classe;
– E' presente una variabile che tiene il valore ritornato dallo yield;
– E' presente una variabile che indica da dove il metodo deve continuare la prossima volta che è invocato.

Quando il chiamante del metodo contenente lo yield è in esecuzione e incontra il ciclo con il foreach, viene creato un oggetto istanziando questa nuova classe.
Per chiarire l'idea, scendiamo più in dettaglio, utilizzando il tool ILDASM, e prendendo a titolo di esempio il seguente frammento di codice che restituisce un'enumerazione dei numeri interi a 32 bit:

using System;
using System.Collections.Generic;
using System.Text;

namespace Esercizio2
{
public class EnumeratoreEs2<T>
{
public static IEnumerable<int> EnumInteri()
{
for (int i = Int32.MinValue; i <= Int32.MaxValue; i++)
yield return i;
}
}

class TestIterator{
static void Main(string[] args)
{
Console.WriteLine("\nEnumerazione degli Interi:");
foreach (int i in EnumeratoreEs2<int>.EnumInteri())
Console.Write("{0} ", i);
Console.WriteLine();
return;
}
}
}

Aprendo il .exe tramite Ildasm:

yield

Come si nota, il metodo EnumInteri() è stato trasformato a livello IL in una vera e propria classe: <EnumInteri>d__0<T> . Vi si trovano le variabili (rombi color ciano) e i metodi ( quadrati fucsia ) necessari all'esecuzione. In questa trasformazione da metodo in classe, sono di solito presenti:

- variabili a cui corrisponderebbero eventuali parametri formali del metodo originale;
- una variabile _current che contiene il valore restituito da yield;
- una variabile che tiene traccia da dove il codice prosegue la sua esecuzione dopo lo yield;
- In questo caso, è il metodo MoveNext() a compiere il lavoro del metodo originale e ritorna un valore booleano. Tale valore è FALSE se il metodo ha completato l'esecuzione. TRUE se ha ancora elementi da esaminare. Il chiamante del metodo EnumInteri(), nel main, crea un'istanza della classe suddetta e invoca il metodo MoveNext().

Analizziamo il metodo main tramite Ildasm:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 88 (0x58)
.maxstack 2
.locals init ([0] int32 i,[1] class mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0000, [2] bool CS$4$0001)
IL_0000: nop
IL_0001: ldstr "\nEnumerazione degli Interi:"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: nop
IL_000d: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> class
Esercizio2.EnumeratoreEs2`1<int32>::EnumInteri()
IL_0012: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class
[mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
IL_0017: stloc.1
.try
{
IL_0018: br.s IL_0032
IL_001a: ldloc.1
IL_001b: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
IL_0020: stloc.0
IL_0021: ldstr "{0} "
IL_0026: ldloc.0
IL_0027: box [mscorlib]System.Int32
IL_002c: call void [mscorlib]System.Console::Write(string,object)
IL_0031: nop
IL_0032: ldloc.1
IL_0033: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_0038: stloc.2
IL_0039: ldloc.2
IL_003a: brtrue.s IL_001a
IL_003c: leave.s IL_004e
} // end .try
finally
{
IL_003e: ldloc.1
IL_003f: ldnull
IL_0040: ceq
IL_0042: stloc.2
IL_0043: ldloc.2
IL_0044: brtrue.s IL_004d
IL_0046: ldloc.1
IL_0047: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_004c: nop
IL_004d: endfinally
} // end handler
IL_004e: nop
IL_004f: call void [mscorlib]System.Console::WriteLine()
IL_0054: nop
IL_0055: br.s IL_0057
IL_0057: ret
} // end of method TestIterator::Main

E' creata un'istanza di un oggetto della nuova classe tramite il metodo getEnumerator. Dopodiché è invocato su questo oggetto il metodo GetCurrent che accede alla variabile _current, in modo da restituirne il valore al chiamante. E' poi invocato il metodo MoveNext() che è implementato come una macchina a stati. Se restituisce TRUE è rieseguito il codice a partire dall'istruzione IL_001a, altrimenti significa che non ha più elementi da scorrere e salta all'istruzione IL_004e. Va notato che il compilatore fa sì che ad ogni ciclo si effettui la Dispose. Si potrebbe estendere la VM in modo da gestire le coroutine. Le coroutine si differenziano dalle subroutine perché, mentre quest'ultime hanno un singolo entry point e possono ritornare solo una volta, le coroutine hanno un primo entry point stabilito, ma possono restituire il controllo più volte e proseguire poi l'esecuzione, che riprende dallo stesso stato di quando era terminata precedentemente. Con la VM attuale, al momento in cui un metodo ritorna, il suo ambiente di esecuzione è perduto.
Un modo per gestire le coroutine richiede l'allocazione di ulteriori stack per ogni coroutine, in modo da non perdere lo stato fra due esecuzioni successive. Si avrebbe così il vantaggio di poter mantenere un ambiente locale a una funzione fra esecuzioni di una stessa coroutine in momenti differenti o anche fra coroutine diverse. Sarebbe anche possibile modificare lo stato di una coroutine, per particolari operazioni sugli iteratori, come ad esempio riportare un iteratore allo stato di partenza.

 

REFERENCES

• Implementation of Iterators in C#2.0,
http://www.thinkingms.com/pensieve/CommentView,guid,fd10bfa8-1aeb-4353-84c8-
cd80e418424f.aspx


• Bart De Smet, C# 2.0 Iterators.
http://community.bartdesmet.net/blogs/bart/archive/2006/07/06/4121.aspx

=======================================================

PDF version [ .pdf ] .