78 votes

En C#, pourquoi ne puis-je pas modifier le membre d'une instance de type valeur dans une boucle foreach ?

Je sais que les types de valeurs doivent être immuables, mais c'est juste une suggestion, pas une règle, n'est-ce pas ? Alors pourquoi je ne peux pas faire quelque chose comme ça :

struct MyStruct
{
    public string Name { get; set; }
}

 public class Program
{
    static void Main(string[] args)
    {
        MyStruct[] array = new MyStruct[] { new MyStruct { Name = "1" }, new MyStruct { Name = "2" } };
        foreach (var item in array)
        {
            item.Name = "3";
        }
        //for (int i = 0; i < array.Length; i++)
        //{
        //    array[i].Name = "3";
        //}

        Console.ReadLine();
    }
}

La boucle foreach dans le code ne compile pas alors que la boucle for commentée fonctionne bien. Le message d'erreur :

Impossible de modifier les membres de 'item' car il s'agit d'une 'variable d'itération foreach'.

Pourquoi ça ?

6 votes

+1 : Bonne question. Je sais depuis longtemps qu'une foreach la variable de boucle ne pouvait pas être modifiée, mais je n'ai jamais vraiment appris pourquoi !

82voto

Kyte Points 855

Parce que foreach utilise un énumérateur, et les énumérateurs ne peuvent pas changer la collection sous-jacente, mais peut cependant, modifier tout objet référencé par un objet dans la collection. C'est là que les sémantiques de type Valeur et Référence entrent en jeu.

Sur un type de référence, c'est-à-dire une classe, tout ce que la collection stocke est une référence à un objet. En tant que telle, elle ne touche jamais les membres de l'objet et ne peut pas s'en soucier. Une modification de l'objet ne touchera pas la collection.

En revanche, les types de valeurs stockent toute leur structure dans la collection. Vous ne pouvez pas toucher à ses membres sans modifier la collection et invalider l'énumérateur.

De plus, l'énumérateur renvoie une copie de la valeur dans la collection. Dans un ref-type, cela ne signifie rien. Une copie d'une référence sera la même référence, et vous pouvez modifier l'objet référencé de la manière que vous voulez, les changements se propageant hors de la portée. Dans un type valeur, par contre, cela signifie que tout ce que vous obtenez est une copie de l'objet, et donc que tout changement sur ladite copie ne se propagera jamais.

1 votes

Excellente réponse. et où trouver plus de preuves sur "les types de valeurs stockent leur structure entière dans la collection" ?

0 votes

@CuiPengFei : Les types de valeurs stockent leur valeur entière dans une variable. Les tableaux sont juste un bloc de variables.

0 votes

Exactement. Un type de valeur conservera toute sa structure dans le conteneur dans lequel il est placé, qu'il s'agisse d'une collection, d'une variable locale, etc. Incidemment, c'est pourquoi il est préférable de rendre les structures immuables : Vous vous retrouvez avec des tonnes de copies du "même" objet et il est difficile de savoir lequel a subi le changement, s'il va se propager et où. Vous savez, les problèmes typiques qui se produisent avec les chaînes de caractères.

19voto

Adam Robinson Points 88472

C'est une suggestion dans le sens où rien ne vous empêche de la violer, mais il faudrait vraiment lui accorder beaucoup plus de poids que "c'est une suggestion". Par exemple, pour les raisons que vous voyez ici.

Les types de valeur stockent la valeur réelle dans une variable, et non une référence. Cela signifie que vous avez la valeur dans votre tableau, et que vous avez une variable de type copie de cette valeur dans item pas une référence. Si vous étiez autorisé à modifier les valeurs dans item il ne sera pas répercuté sur la valeur du tableau puisqu'il s'agit d'une valeur entièrement nouvelle. C'est pourquoi cela n'est pas autorisé.

Si vous voulez faire cela, vous devrez boucler sur le tableau par index et ne pas utiliser de variable temporaire.

0 votes

0 votes

@Steven : La raison que j'ai mentionnée est correcte, même si elle n'est pas aussi bien décrite que dans la réponse liée.

0 votes

Tout ce que tu as expliqué me semble correct, sauf cette phrase "C'est pourquoi ce n'est pas autorisé". ça ne me semble pas être la raison, je n'en suis pas sûr, mais ça ne me semble pas l'être. et la réponse de Kyte l'est davantage.

13voto

Margus Points 6175

Les structures sont des types de valeurs.
Les classes sont des types de référence.

ForEach construire des utilisations IEnumerator de IEnumerable éléments de type de données. Lorsque cela se produit, les variables sont en lecture seule dans le sens où vous ne pouvez pas les modifier, et comme vous avez un type de valeur, vous ne pouvez pas modifier une valeur contenue, car elle partage la même mémoire.

C# lang spec section 8.8.4 :

La variable d'itération correspond à une variable locale en lecture seule dont la portée s'étend sur l'instruction intégrée.

Pour résoudre ce problème, utilisez la classe au lieu de la structure ;

class MyStruct {
    public string Name { get; set; }
}

Editer : @CuiPengFei

Si vous utilisez var type implicite, il est plus difficile pour le compilateur de vous aider. Si vous utilisiez MyStruct il vous dirait, dans le cas d'une structure, qu'elle est en lecture seule. Dans le cas des classes, la référence à l'élément est en lecture seule, vous ne pouvez donc pas écrire item = null; à l'intérieur de la boucle, mais vous pouvez changer ses propriétés, qui sont mutables.

Vous pouvez également utiliser (si vous aimez utiliser struct ) :

        MyStruct[] array = new MyStruct[] { new MyStruct { Name = "1" }, new MyStruct { Name = "2" } };
        for (int index=0; index < array.Length; index++)
        {
            var item = array[index];
            item.Name = "3";
        }

0 votes

Merci, mais comment expliquez-vous le fait que si j'ajoute une méthode ChangeName à MyStruct et que je l'appelle dans la boucle foreach, cela fonctionnera parfaitement ?

3voto

Ian Points 13892

NOTA: Conformément au commentaire d'Adam, ce n'est pas vraiment la bonne réponse/la cause du problème. Mais c'est tout de même quelque chose qu'il faut garder à l'esprit.

De MSDN . Vous ne pouvez pas modifier les valeurs lorsque vous utilisez l'Enumérateur, ce qui est essentiellement ce qui foreach fait.

Les énumérateurs peuvent être utilisés pour lire les données de la collection, mais ils ne peuvent pas être utilisés pour modifier la collection sous-jacente.

Un énumérateur reste valide tant que la collection reste inchangée. Si des changements sont apportés à la collection, comme l'ajout, la modification ou la suppression d'éléments éléments, l'énumérateur est irrémédiablement invalidé et son comportement est indéfini.

1 votes

Bien que cela puisse en avoir l'air, ce n'est pas le problème dans la question. Vous pouvez certainement modifier les valeurs des propriétés ou des champs d'une instance obtenue par le biais de la fonction foreach (ce n'est pas ce dont parle l'article du MSDN), parce que cela modifie le fichier membre de la collection, et non la collection elle-même. Il s'agit ici de la sémantique du type valeur par rapport au type référence.

0 votes

En effet, je viens de lire votre réponse et je me suis rendu compte de mon erreur. Je vais laisser cette réponse en place car elle reste utile.

0 votes

Merci, mais je ne pense pas que ce soit la raison. parce que j'essaye de changer un membre de l'élément de la collection, pas la collection elle-même. et si je change MyStruct d'une struct à un

1voto

Adrian Carneiro Points 26652

Faites de MyStruct une classe (au lieu de struct) et vous serez en mesure de le faire.

0 votes

C'est exact si l'on se base sur le VT (type de valeur) par rapport au RT (type de référence), mais cela ne répond pas vraiment à la question.

0 votes

Merci, je sais que ça va marcher. Mais j'essaie juste de trouver la raison.

0 votes

@CuiPengFei : Ma réponse explique pourquoi cela se produit.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X