Y a-t-il une raison pour que C#réutilise la variable dans un foreach?

Lorsqu'on utilise des expressions lambda ou des méthodes anonymes en C#, il faut se méfier de l'accès à la fermeture modifiée pitfall. 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 fera que toutes les clauses Where de la requête seront basées sur la valeur finale de s .

comme expliqué ici , cela se produit parce que la variable s déclaré dans la boucle foreach ci-dessus est traduit comme ceci dans le compilateur:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}

au lieu de comme ceci:

while (enumerator.MoveNext())
{
   string s;
   s = enumerator.Current;
   ...
}

comme indiqué ici , il n'y a aucun avantage de performance à déclarer une variable en dehors de la boucle, et dans des circonstances normales la seule raison que je peux penser à faire ceci est si vous projetez d'utiliser la variable en dehors de la portée de la boucle:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}
var finalString = s;

toutefois, les variables définies dans une boucle foreach 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 bénéfice perceptible.

y a-t-il quelque chose que vous pouvez faire avec des boucles foreach de cette façon que vous ne pourriez pas si elles étaient compilées avec une variable à Scope interne, ou est-ce juste une le choix arbitraire qui a été fait avant que les méthodes anonymes et les expressions lambda soient disponibles ou courantes, et qui n'a pas été révisé depuis?

1511
demandé sur Community 2012-01-17 21:21:07
la source

5 ответов

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 bénéfice perceptible.

votre Critique est entièrement justifiée.

je discute de ce problème en détail ici:

fermeture sur la boucle variable considérée comme nuisible

est là quelque chose que vous pouvez faire avec les boucles foreach de cette façon que vous ne pourriez pas si elles ont été compilées avec une variable interne-scoped? ou est-ce un choix arbitraire qui a été fait avant que les méthodes anonymes et les expressions lambda soient disponibles ou courantes, et qui n'a pas été révisé depuis?

ce dernier. La spécification C# 1.0 ne précisait pas si la variable de boucle se trouvait à l'intérieur ou à l'extérieur du corps de boucle, car elle ne faisait aucune différence observable. Lors de la fermeture la sémantique a été introduite dans C # 2.0, le choix a été fait de mettre la variable boucle en dehors de la boucle, compatible avec la boucle "pour".

je pense qu'il est juste de dire que tous le regretter. C'est l'une des pires erreurs en C#, et nous allons prendre de la modification de rupture pour le fixer. en C# 5 la variable de boucle foreach sera logiquement à l'intérieur de le corps de la boucle, et donc les fermetures obtiendront une nouvelle copie chaque fois.

la boucle for ne sera pas modifiée, et la modification ne sera pas "rétroportée" aux versions précédentes de C#. Vous devez donc continuer à être prudent lorsque vous utilisez cet idiome.

1295
répondu Eric Lippert 2013-09-11 20:20:55
la source

ce que vous demandez est entièrement couvert par Eric Lippert dans son billet de blog Closing over the loop variable considered harmful et sa suite.

pour moi, l'argument le plus convaincant est qu'avoir une nouvelle variable dans chaque itération serait incompatible avec la boucle de style for(;;) . Vous attendez - vous à avoir un nouveau int i dans chaque itération de for (int i = 0; i < 10; i++) ?

le problème Le plus commun avec ce comportement est faire une fermeture variable d'itération et il a une simple solution de contournement:

foreach (var s in strings)
{
    var s_for_closure = s;
    query = query.Where(i => i.Prop == s_for_closure); // access to modified closure

mon billet de blog sur cette question: fermeture sur foreach variable dans C# .

174
répondu Krizz 2013-10-29 01:36:36
la source

ayant été mordu par ceci, j'ai l'habitude d'inclure des variables définies localement dans la portée la plus interne que j'utilise pour transférer à n'importe quelle fermeture. Dans votre exemple:

foreach (var s in strings)
{
    query = query.Where(i => i.Prop == s); // access to modified closure

je n':

foreach (var s in strings)
{
    string search = s;
    query = query.Where(i => i.Prop == search); // New definition ensures unique per iteration.

une fois que vous avez cette habitude, vous pouvez l'éviter dans le très cas rare que vous avez réellement l'intention de lier aux lunettes extérieures. Pour être honnête, je ne pense pas que j'ai jamais fait.

95
répondu Godeke 2014-01-15 18:06:01
la source

dans C# 5.0, ce problème est corrigé et vous pouvez fermer les variables de boucle et obtenir les résultats que vous attendez.

la spécification de langue dit:

8.8.4 la déclaration foreach

(...)

Une instruction foreach de la forme

foreach (V v in x) embedded-statement

est alors étendu à:

{
  E e = ((C)(x)).GetEnumerator();
  try {
      while (e.MoveNext()) {
          V v = (V)(T)e.Current;
          embedded-statement
      }
  }
  finally {
      … // Dispose e
  }
}

(...)

le placement de v à l'intérieur de la boucle while est important pour la façon dont il est capté par toute fonction anonyme se produisant dans le déclaration intégrée. Par exemple:

int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
    if (f == null) f = () => Console.WriteLine("First value: " + value);
}
f();

si v était déclaré en dehors de la boucle while, il serait partagé parmi toutes les itérations, et de sa valeur après la boucle serait la valeur finale, 13 , qui est ce que l'invocation de f imprimerait. Au lieu de cela, parce que chaque itération a sa propre variable v , celle capturé par f dans la première itération continuera à tenir la valeur 7 , qui est ce qui sera imprimé. ( Note: versions antérieures de C# déclaré v en dehors de la boucle while. )

52
répondu Paolo Moretti 2012-09-03 17:58:51
la source

à mon avis, c'est une question étrange. Il est bon de savoir comment fonctionne le compilateur mais que seulement "bon à savoir".

si vous écrivez du code qui dépend de l'algorithme du compilateur c'est une mauvaise pratique.Et il est préférable de réécrire le code pour exclure cette dépendance.

c'est une bonne question pour un entretien d'embauche. Mais dans la vraie vie, je n'ai pas eu de problèmes que j'ai résolus lors d'un entretien d'embauche.

les 90% des emplois pour chaquepays élément de collection (pas de sélectionner ou de calculer des valeurs). parfois, vous devez calculer certaines valeurs à l'intérieur de la boucle, mais ce n'est pas une bonne pratique de créer une grande boucle.

il est préférable d'utiliser des expressions LINQ pour calculer les valeurs. Parce que quand vous calculez beaucoup de chose à l'intérieur de la boucle, après 2-3 mois quand vous (ou quelqu'un d'autre) Lira cette personne de code ne comprendra pas ce qui est ceci et comment cela devrait fonctionner.

-1
répondu TemaTre 2018-06-15 10:31:34
la source

Autres questions sur c# lambda scope foreach anonymous-methods