Je reçois l'avertissement suivant:
Accès à foreach variable en fermeture. Peut avoir un comportement différent lorsqu'il est compilé avec différentes versions du compilateur.
Voici à quoi cela ressemble dans mon éditeur:
Je sais comment résoudre cet avertissement, mais je veux savoir pourquoi j'obtiendrais cet avertissement?
S'agit-il de la version "CLR"? Est-ce lié à "IL"?
Réponses:
Cet avertissement comporte deux parties. Le premier est ...
... ce qui n'est pas invalide en soi, mais il est contre-intuitif à première vue. Il est également très difficile de bien faire. (À tel point que l'article auquel je renvoie ci-dessous décrit cela comme "nuisible".)
Prenez votre requête, en notant que le code que vous avez extrait est essentiellement une forme développée de ce que le compilateur C # (avant C # 5) génère pour
foreach
1 :Eh bien, il est syntaxiquement valide. Et si tout ce que vous faites dans votre boucle utilise la valeur de
s
alors tout est bon. Mais la fermetures
mènera à un comportement contre-intuitif. Jetez un œil au code suivant:var countingActions = new List<Action>(); var numbers = from n in Enumerable.Range(1, 5) select n.ToString(CultureInfo.InvariantCulture); using (var enumerator = numbers.GetEnumerator()) { string s; while (enumerator.MoveNext()) { s = enumerator.Current; Console.WriteLine("Creating an action where s == {0}", s); Action action = () => Console.WriteLine("s == {0}", s); countingActions.Add(action); } }
Si vous exécutez ce code, vous obtiendrez la sortie de console suivante:
Creating an action where s == 1 Creating an action where s == 2 Creating an action where s == 3 Creating an action where s == 4 Creating an action where s == 5
C'est ce à quoi vous vous attendez.
Pour voir quelque chose auquel vous ne vous attendez probablement pas, exécutez le code suivant immédiatement après le code ci-dessus:
foreach (var action in countingActions) action();
Vous obtiendrez la sortie de console suivante:
s == 5 s == 5 s == 5 s == 5 s == 5
Pourquoi? Parce que nous avons créé cinq fonctions qui font toutes exactement la même chose: afficher la valeur de
s
(que nous avons clôturée). En réalité, il s'agit de la même fonction ("Imprimers
", "Imprimers
", "Imprimers
" ...).Au moment où nous allons les utiliser, ils font exactement ce que nous demandons: imprimer la valeur de
s
. Si vous regardez la dernière valeur connue des
, vous verrez que c'est5
. Nous sommes doncs == 5
imprimés cinq fois sur la console.C'est exactement ce que nous avons demandé, mais probablement pas ce que nous voulons.
La deuxième partie de l'avertissement ...
... c'est ce que c'est. À partir de C # 5, le compilateur génère un code différent qui "empêche" que cela se produise via
foreach
.Ainsi, le code suivant produira des résultats différents sous différentes versions du compilateur:
foreach (var n in numbers) { Action action = () => Console.WriteLine("n == {0}", n); countingActions.Add(action); }
Par conséquent, il produira également l'avertissement R # :)
Mon premier extrait de code, ci-dessus, présentera le même comportement dans toutes les versions du compilateur, puisque je n'utilise pas
foreach
(je l'ai plutôt développé comme le font les compilateurs pré-C # 5).Je ne sais pas trop ce que vous demandez ici.
Le message d'Eric Lippert indique que le changement se produit "en C # 5". Donc
vous devez probablement cibler .NET 4.5 ou version ultérieureavec un compilateur C # 5 ou version ultérieure pour obtenir le nouveau comportement, et tout ce qui précède obtient l'ancien comportement.Mais pour être clair, c'est une fonction du compilateur et non de la version .NET Framework.
Un code différent produit un IL différent, donc dans ce sens il y a des conséquences pour l'IL généré.
1
foreach
est une construction beaucoup plus courante que le code que vous avez publié dans votre commentaire. Le problème survient généralement par l'utilisation deforeach
, et non par une énumération manuelle. C'est pourquoi les modifications apportées àforeach
C # 5 permettent d'éviter ce problème, mais pas complètement.la source
foreach
truc ici vient du contenu de la question. Vous avez raison de dire que cela peut se produire de diverses manières, plus générales.La première réponse est excellente, alors j'ai pensé ajouter juste une chose.
Vous recevez l'avertissement car, dans votre exemple de code, ReflectModel se voit attribuer un IEnumerable, qui ne sera évalué qu'au moment de l'énumération, et l'énumération elle-même peut se produire en dehors de la boucle si vous affectez ReflectModel à quelque chose avec une portée plus large .
Si vous avez changé
...Where(x => x.Name == property.Value)
à
...Where(x => x.Name == property.Value).ToList()
alors ReflectModel se verrait attribuer une liste définie dans la boucle foreach, vous ne recevrez donc pas l'avertissement, car l'énumération se produirait certainement dans la boucle, et non en dehors.
la source
Une variable à portée de bloc doit résoudre l'avertissement.
foreach (var entry in entries) { var en = entry; var result = DoSomeAction(o => o.Action(en)); }
la source