Les Closures C# avant le framework .Net 4.5 Partie1 (Un comportement plutôt curieux)

Définition d’une fermeture (closure en anglais) selon Wikipedia

En informatique, une fermeture est une fonction de première classe avec des variables libres liées dans l’environnement lexical

Une fonction de première classe… ? Variables libres liéés dans l’environnement lexical…??

Une fonction de première classe signifie simplement qu’il s’agit d’une fonction concideré par C# (ou autre langage) en tant que type de donnée de première classe…

En termes plus simples, cela signifie qu’on peu assigner une fonction à une variable.

Variable libre

C’est une variable référencée dans une fonction. En revanche, cette variable n’est pas un paramètre ni une variable locale à la fonction. Elle y est juste référencée et utilisée.

Exemples

Dans ReturnFunc() y est référencée la variable ‘libre’ localvariable.

localvariable n’est pas un paramètre ni une variable locale au delegate(fonction anonyme) qui l’utilise.

Son rôle dans le delegate : s’incrémenter et se multiplier avec une variable locale au delegate.


private static Func<int, int> ReturnFunc()
{
    var localvariable = 1;

    //'Free' localvariable is used inside of the delegate 
    Func<int, int> myfunc = delegate(int var1) 
    {
        Console.WriteLine("Value of localvariable: "+ localvariable);
        localvariable += 1;
        return var1 * localvariable;
    };

    //When we return the myfunc, localvariable is bound inside of the delegate
    return myfunc;
}

Nous faisons 2 appels successifs à ReturnFunc().

La sortie devrait être : 4 [(1+1)*2] puis 8 [(1+1)*4]


static void Main(string[] args)
{
    //First class function assigned to variable v
    var v = ReturnFunc();

    Console.WriteLine("First call");
    Console.WriteLine("Result: "+v(2));

    Console.WriteLine("Second call");
    Console.WriteLine("Result: "+v(4));

    Console.Read();
}

Sortie

First call
Value of localvariable: 1
Result: 4
Second call
Value of localvariable: 2
Result: 12

A chaque appel, on s’aperçoit que localvariable conserve sa valeure précédente avant de s’incrémenter !

Comportement étrange

Les variables locales ne sont-elles pas créées sur la pile? Ne partent-elles pas lorsque nous avons fini d’exécuter la méthode?

En d’autres termes, localvariable devrait être reinitialisée à chaque appel à ReturnFunc(), et donc valoir 1 !

Deduction

La variable libre localvariable vit maintenant avec la méthode renvoyé par ReturnFunc() : localvariable y est liée en dehors de sa portée d’origine.

Voilà ce qu’est une closure ! la variable libre localvariable est liée dans l’environnement lexical!

Un autre exemple

On créer une liste de fonction anonyme (‘Action’)

On utilise une boucle qui contient une variable libre ‘i’.

A chaque tour de boucle, on ajoute à la liste une fonction anonyme qui utilise la variable libre ‘i’

static void Main(string[] args)
{
    var listAction = new List<Action>;
		
    for(int i=0; i<10; i++)
    {
 	//'Free' i, is used inside of the delegate
	listAction.Add(() => Console.WriteLine(i));
    }

    foreach(Action item in listAction)
    {
	//When we return the item, i is bound inside of the delegate
	item();
    }

    Console.Read();
}

Sortie

Si nous n’étions pas avisé au sujet des closures, la sortie attendu serait 0 1 2 3 4 5 6 7 8 9

Mais nous savons que la variable libre ‘i’ est liée dans l’environnement lexical de la fonction anonyme ()=>console.WriteLine(i)

Nous observons le même phénomène.

10
10
10
10
10
10
10
10
10
10

En d’autres termes :

  • Dans la première boucle, la variable libre ‘i’ s’itère de 0 à 9,
  • La variable libre ‘i’ est l’utilisé dans l’expression lambda (la fonction anonyme). Chaque lambda obtient son accès à ‘i’ via la closure.
  • A la fin de cette première boucle, ‘i’ = 10 (10 car on utilise l’opérateur post-incrémentation: i++ : lors de la dernière itération de la boucle, on augmente le compteur à 10 et on vérifie si ‘i'<10 ce qui n’est plus le cas, donc sortie de boulce)
  • Ensuite, les ‘Action’ (ou fonctions anonymes ()=>Console.WriteLine(i)) s’executent : *elles ont toutes une closure sur la même variable libre ‘i’ qui vaut 10.
  • Chaque ‘Action’ (fonction anonymes) affiche donc sa valeur actuelle : 10 sur la console.

*Point important pour comprendre la solution de contournement évoquée en partie 2

Leave a Reply

Your email address will not be published. Required fields are marked *