web-dev-qa-db-fra.com

Est-ce que foreach () itère par référence?

Considère ceci:

List<MyClass> obj_list = get_the_list();
foreach( MyClass obj in obj_list )
{
    obj.property = 42;
}

'Obj' est-il une référence à l'objet correspondant dans la liste de sorte que lorsque je modifie la propriété, la modification persistera dans l'instance d'objet une fois construite quelque part?

64
sharkin

Oui, obj est une référence à l'objet courant dans la collection (en supposant que MyClass est en fait une classe). Si vous modifiez des propriétés via la référence, vous modifiez l'objet, comme vous vous y attendez.

Sachez toutefois que vous ne pouvez pas modifier la variable obj elle-même car il s'agit de la variable d'itération. Vous obtiendrez une erreur de compilation si vous essayez. Cela signifie que vous ne pouvez pas l'annuler et si vous itérez des types de valeur, vous ne pouvez modifier aucun membre car cela changerait la valeur.

La spécification du langage C # indique (8.8.4)

"La variable d'itération correspond à une variable locale en lecture seule avec une portée qui s'étend sur l'instruction incorporée."

61
Brian Rasmussen

Oui, jusqu'à ce que vous changiez le type générique de List en IEnumerable ..

21
jaccso

Vous avez posé 2 questions différentes ici, prenons-les dans l'ordre.

Une boucle foreach itère-t-elle par référence?

Si vous voulez dire dans le même sens qu'un C++ pour boucle par référence, alors non. C # n'a pas de références de variables locales au même sens que C++ et ne prend donc pas en charge ce type d'itération.

Le changement va-t-il persister

En supposant que MyClass est un type de référence, la réponse est oui. Une classe est un type de référence dans .Net et donc la variable d'itération est une référence à une variable, pas une copie. Ce ne serait pas vrai pour un type de valeur.

19
JaredPar

Eh bien, il m'est arrivé que mes modifications n'étaient pas mises à jour dans une boucle foreach lorsque j'ai itéré via la collection var:

var players = this.GetAllPlayers();
foreach (Player player in players)
{
    player.Position = 1;
}

Lorsque j'ai changé var en List, il a commencé à fonctionner.

15
mrzepa

Vous pouvez dans ce cas (en utilisant un List<T>) mais si vous deviez parcourir le générique IEnumerable<T> puis il dépend de sa mise en œuvre.

Si c'était encore un List<T> ou T[] par exemple, tout fonctionnerait comme prévu. Le gros problème vient quand vous travaillez avec un IEnumerable<T> qui a été construit en utilisant le rendement. Dans ce cas, vous ne pouvez plus modifier les propriétés de T dans une itération et vous attendre à ce qu'elles soient présentes si vous répétez la même IEnumerable<T> encore.

6
J Keegan

cela est vrai tant qu'il ne s'agit pas d'une structure.

3
Deepfreezed

Il est peut-être intéressant pour vous de comprendre que la version C # 7.3 permet de modifier les valeurs par référence à condition que la propriété Current de l'énumérateur renvoie un type de référence. Les éléments suivants seraient valides (copie textuelle des documents MS):

Span<int> storage = stackalloc int[10];
int num = 0;
foreach (ref int item in storage)
{
    item = num++;
}

Pour en savoir plus sur cette nouvelle fonctionnalité, consultez instruction C # foreach | Microsoft Docs .

3
Wolf

Eh bien, sans comprendre exactement ce que vous entendez par "Itérer par référence", je ne peux pas répondre spécifiquement oui ou non, mais je peux dire que ce qui se passe sous la surface est que le framework .net construit une classe "énumérateur" pour chaque fois que le code client appelle un foreach, pour la durée de vie du foreach, qui maintient un pointeur de référence dans la collection en cours d'itération, et chaque fois que votre foreach itère, ir "délivre" un élément et "incrémente" le pointeur ou la référence dans le énumérateur à l'élément suivant ...

Cela se produit indépendamment du fait que les éléments de la collection que vous parcourez sont des types de valeurs ou des types de référence.

2
Charles Bretana

obj est une référence à un élément dans la liste, donc si vous changez sa valeur, il persistera. Maintenant, ce qui devrait vous préoccuper, c'est de savoir si get_the_list (); fait une copie complète de la liste ou renvoie la même instance.

0
Stan R.

Oui, c'est aussi pourquoi vous ne pouvez pas modifier l'objet énumérable dans le contexte de l'instruction foreach.

0
Brian Scott