Comment puis-je capturer la valeur d'une variable externe dans une expression lambda?
Je viens de rencontré le problème suivant:
for (var i = 0; i < 50; ++i) {
Task.Factory.StartNew(() => {
Debug.Print("Error: " + i.ToString());
});
}
Entraînera dans une série de "Erreur: x", où la plupart des x sont égales à 50.
De la même façon:
var a = "Before";
var task = new Task(() => Debug.Print("Using value: " + a));
a = "After";
task.Start();
En résulte "à l'Aide de la valeur: Après".
Cela signifie clairement que la concaténation dans l'expression lambda ne se produit pas immédiatement. Comment est-il possible d'utiliser une copie de l'extérieur de la variable dans l'expression lambda, au moment où l'expression est déclarée? Le code suivant ne fonctionnera pas mieux (ce qui n'est pas forcément incohérent, je l'avoue):
var a = "Before";
var task = new Task(() => {
var a2 = a;
Debug.Print("Using value: " + a2);
});
a = "After";
task.Start();
source d'informationauteur Erwin Mayer
Vous devez vous connecter pour publier un commentaire.
Cela a plus à voir avec les lambdas que le filetage. Un lambda de capture de la référence à une variable, pas la valeur de la variable. Cela signifie que lorsque vous essayez d'utiliser je dans votre code, sa valeur sera tout ce qui a été stocké dans je dernier.
Pour éviter cela, vous devez copier la valeur de la variable à une variable locale lorsque le lambda commence. Le problème est, à partir d'une tâche de la surcharge et de la première copie peut être effectuée qu'après que la boucle se termine. Le code suivant échouera également
Que James Manning noté, vous pouvez ajouter une variable locale à la boucle et la copie de la variable de boucle. De cette façon, vous êtes la création de 50 différentes variables pour contenir la valeur de la variable de boucle, mais au moins, vous obtenez le résultat attendu. Le problème est, vous avez une beaucoup de des attributions supplémentaires.
La meilleure solution est de passer le paramètre boucle comme un paramètre d'état:
À l'aide d'un paramètre d'état de résultats en moins de dotations. En regardant le code décompilé:
C'est parce que vous exécutez le code dans un nouveau thread, et le thread principal va immédiatement changer la variable. Si l'expression lambda ont été exécutés immédiatement, tout le point de l'utilisation d'une tâche serait perdu.
Le thread n'a pas sa propre copie de la variable au moment de la création de la tâche, toutes les tâches d'utiliser la même variable (qui est stocké dans la fermeture de la méthode, ce n'est pas une variable locale).
Les expressions Lambda ne capture pas la valeur de la variable externe, mais une référence à elle. C'est la raison pour laquelle vous ne voyez
50
ouAfter
dans vos tâches.Pour résoudre ce avant de créer votre expression lambda une copie de la capture par valeur.
Ce malheureux comportement sera fixé par le compilateur C# avec .NET 4.5, jusqu'alors, vous avez besoin de vivre avec cette bizarrerie.
Exemple:
Les expressions Lambda sont, par définition, paresseusement évaluées de sorte qu'ils ne seront pas évaluées jusqu'à ce que appelé le. Dans votre cas, par l'exécution de la tâche. Si vous fermez sur un local dans votre expression lambda de l'état des locaux au moment de l'exécution sera pris en compte. Qui est ce que vous voyez. Vous pouvez prendre avantage de cela. E. g. votre boucle for n'avez pas vraiment besoin d'une nouvelle lambda pour chaque itération en supposant que pour le bien de l'exemple que le décrit résultat a été ce que vous avez prévu, vous pourriez écrire
d'autre part, si vous souhaité qu'il en fait imprimé
"Error: 1"..."Error 50"
vous pouvez changer le ci-dessus pourLa première se referme sur
i
et d'utiliser l'état dei
au moment de l'exécution de l'Action et de l'etat est souvent à l'état après la boucle se termine. Dans ce dernier casi
est évaluée avec impatience parce que c'est passé comme argument à une fonction. Cette fonction retourne alors unAction<int>
qui est passé àStartNew
.De sorte que la décision de conception le rend à la fois paresseusement évaluation et désireux d'évaluation possible. Paresseusement, car les habitants sont fermées durant la et ardemment parce que vous pouvez forcer les habitants à être exécuté en les passant comme argument ou comme indiqué ci-dessous déclarant un autre local avec une courte portée
Tous les ci-dessus est générale pour les Lambdas. Dans le cas spécifique de
StartNew
il y a effectivement une surcharge qui fait ce que le deuxième exemple, le fait que l'on peut simplifier à