This article is available in english.
Après une petite discussion avec
Eric Lippert à propos d'un
post sur l'utilisation dans une expression lambda d'une variable locale déclarée dans une boucle foreach, Eric m'a fait remarquer que le morceau de code suivant :
int a = 0;
Action action = () => Console.WriteLine(a);
action();
N'est pas étendu par le compilateur vers ce code :
[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
public int a;
public void <Main>b__0()
{
Console.WriteLine(this.a);
}
}
void Main()
{
int a = 0;
var display = new <>c__DisplayClass1();
display.a = a;
var action = new Action(display.<Main>b__0);
action();
}
J'ai fait une supposition sur le fait qu'une variable locale était simplement copiée dans la "display class" lorsqu'elle n'est pas utilisée après la création de la lambda, ce qui n'est pas le cas.
Si l'on prend un exemple un peu différent, avec le code suivant :
int a = 0;
Action action = () => Console.WriteLine(a);
a = 42;
action();
Ma supposition aurait fait en sorte que l'exécution de ce dernier code aurait affiché 0. Or ce n'est pas correct car les expressions lambda (et les méthodes anonymes) "capturent" la variable et non la valeur; l'exécution doit donc afficher 42.
En fait, le dernier morceau de code est étendu de la manière suivante :
var display = new <>c__DisplayClass1();
display.a = 0;
var action = new Action(display.<Main>b__0);
display.a = 42;
action();
On peut constater dans les faits que la variable qui était auparavant locale, donc sur la pile, a été "promue" en tant que champ membre de la "DisplayClass". Cela veut dire aussi que toutes les références faites à cette variable "locale", aussi bien à l'intérieur qu'à l'extérieur de la lambda, sont remplacée vers l'instance courante de "DisplayClass".
C'est finalement très simple, mais on sent que la "magie" derrière le sucre syntaxique de C#3.0 a été assez bien pensée !
Je finirais par un grand merci à Eric Lippert qui a pris le temps de me répondre, malgré qu'il soit très probablement surchargé par le développement de C# 4.0. (avec la contravariance des types génériques, yey !)