Lorsque vous utilisez des expressions lambda ou des méthodes anonymes en C #, nous devons nous méfier de l' accès aux pièges de fermeture modifiés . Par exemple:
foreach (var s in strings)
{
query = query.Where(i => i.Prop == s); // access to modified closure
...
}
En raison de la fermeture modifiée, le code ci-dessus entraînera que toutes les Where
clauses de la requête seront basées sur la valeur finale de s
.
Comme expliqué ici , cela se produit car la s
variable déclarée dans la foreach
boucle ci-dessus est traduite comme ceci dans le compilateur:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
au lieu de comme ça:
while (enumerator.MoveNext())
{
string s;
s = enumerator.Current;
...
}
Comme indiqué ici , il n'y a aucun avantage en termes de performances à déclarer une variable en dehors de la boucle, et dans des circonstances normales, la seule raison pour laquelle je peux penser à cela est si vous prévoyez d'utiliser la variable en dehors de la portée de la boucle:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
var finalString = s;
Cependant les variables définies dans une foreach
boucle ne peuvent pas être utilisées en dehors de la boucle:
foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.
Ainsi, le compilateur déclare la variable d'une manière qui la rend très sujette à une erreur qui est souvent difficile à trouver et à déboguer, tout en ne produisant aucun avantage perceptible.
Y a-t-il quelque chose que vous pouvez faire avec des foreach
boucles de cette façon que vous ne pourriez pas faire si elles étaient compilées avec une variable de portée interne, ou est-ce juste un choix arbitraire qui a été fait avant que les méthodes anonymes et les expressions lambda ne soient disponibles ou communes, et qui n'ont pas pas été révisé depuis?
String s; foreach (s in strings) { ... }
?foreach
mais d'expressions lamda résultant en un code similaire à celui de l'OP ...Réponses:
Votre critique est entièrement justifiée.
Je discute ce problème en détail ici:
La fermeture de la variable de boucle est considérée comme nuisible
Le dernier. La spécification C # 1.0 n'a en fait pas indiqué si la variable de boucle était à l'intérieur ou à l'extérieur du corps de la boucle, car elle ne faisait aucune différence observable. Lorsque la sémantique de fermeture a été introduite en C # 2.0, le choix a été fait de placer la variable de boucle en dehors de la boucle, cohérente avec la boucle "for".
Je pense qu'il est juste de dire que tous regrettent cette décision. C'est l'un des pires "accrochages" en C #, et nous allons prendre le changement de rupture pour le corriger. En C # 5, la variable de boucle foreach sera logiquement à l' intérieur du corps de la boucle, et donc les fermetures obtiendront une nouvelle copie à chaque fois.
La
for
boucle ne sera pas modifiée et la modification ne sera pas "redirigée" vers les versions précédentes de C #. Vous devez donc continuer à être prudent lorsque vous utilisez cet idiome.la source
foreach
est «sûr», maisfor
ne l'est pas.Ce que vous demandez est entièrement couvert par Eric Lippert dans son article de blog Clôture sur la variable de boucle considérée comme nuisible et sa suite.
Pour moi, l'argument le plus convaincant est que le fait d'avoir une nouvelle variable à chaque itération serait incompatible avec la
for(;;)
boucle de style. Vous attendriez-vous à en avoir un nouveauint i
à chaque itération defor (int i = 0; i < 10; i++)
?Le problème le plus courant avec ce comportement est une variable de fermeture sur itération et il a une solution de contournement facile:
Mon article de blog sur ce problème: Clôture de la variable foreach en C # .
la source
ref
.cut
pour les refs / vars etcute
pour garder les valeurs évaluées dans les fermetures partiellement évaluées).Ayant été mordu par cela, j'ai l'habitude d'inclure des variables définies localement dans la portée la plus intérieure que j'utilise pour transférer à n'importe quelle fermeture. Dans votre exemple:
Je fais:
Une fois que vous avez pris cette habitude, vous pouvez l'éviter dans les très rares cas où vous aviez réellement l'intention de vous lier aux portées extérieures. Pour être honnête, je ne pense pas l'avoir fait.
la source
Dans C # 5.0, ce problème est résolu et vous pouvez fermer les variables de boucle et obtenir les résultats attendus.
La spécification de langue dit:
la source