web-dev-qa-db-fra.com

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 ce n'est qu'une suggestion, pas une règle, non? 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 se compile pas tandis que la boucle commentée fonctionne correctement. Le message d'erreur:

Impossible de modifier les membres de 'item' car c'est une 'variable d'itération foreach'

Pourquoi donc?

59

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

Sur un type de référence, c'est-à-dire une classe, toute la collection stockée est une référence à un objet. En tant que tel, il ne touche en fait aucun des membres de l'objet et ne s'en soucie guère. Une modification de l'objet ne touchera pas la collection.

En revanche, les types de valeur stockent l'intégralité de 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 retourne une copie de la valeur dans la collection. Dans un ref-type, cela ne veut rien dire. Une copie d'une référence sera la même référence et vous pouvez modifier l'objet référencé comme vous le souhaitez, les modifications s'étalant hors de la portée. D'un type de valeur, d'autre part, signifie que tout ce que vous obtenez est une copie de l'objet, et donc toute modification sur cette copie ne se propagera jamais.

63
Kyte

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

Les types de valeurs stockent la valeur réelle dans une variable, pas une référence. Cela signifie que vous avez la valeur dans votre tableau et que vous avez copie de cette valeur dans item, pas une référence. Si vous étiez autorisé à modifier les valeurs dans item, cela ne serait pas reflété sur la valeur dans le tableau car il s'agit d'une toute nouvelle valeur. C'est pourquoi ce n'est pas autorisé.

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

16
Adam Robinson

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

ForEach la construction utilise IEnumerator sur 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 aucune valeur contenue, car il partage la même mémoire.

Section 8.8.4 des spécifications C # lang:

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

Pour corriger cette classe d'utilisation insted de structure;

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

Modifier: @CuiPengFei

Si vous utilisez le type implicite var, il est plus difficile pour le compilateur de vous aider. Si vous utilisiez MyStruct il vous dirait en cas de structure, qu'il 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 modifier ses propriétés, qui sont mutables.

Vous pouvez également utiliser (si vous souhaitez 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";
        }
9
Margus

REMARQUE: Selon le commentaire d'Adam, ce n'est pas vraiment la bonne réponse/cause du problème. C'est quand même quelque chose à garder à l'esprit.

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

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 modifications sont apportées à la collection, telles que l'ajout, la modification ou la suppression d'éléments, l'énumérateur est irrémédiablement invalidé et son comportement n'est pas défini.

3
Ian

Les types de valeurs sont appelés par valeur . En bref, lorsque vous évaluez la variable, une copie est effectuée. Même si cela était autorisé, vous éditez une copie plutôt que l'instance d'origine de MyStruct.

foreach est en lecture seule en C # . Pour les types de référence (classe), cela ne change pas beaucoup, car seule la référence est en lecture seule, vous êtes donc toujours autorisé à effectuer les opérations suivantes:

    MyClass[] array = new MyClass[] {
        new MyClass { Name = "1" },
        new MyClass { Name = "2" }
    };
    foreach ( var item in array )
    {
        item.Name = "3";
    }

Cependant, pour les types de valeur (struct), l'objet entier est en lecture seule, ce qui entraîne ce que vous rencontrez. Vous ne pouvez pas ajuster l'objet dans le foreach.

1
Steven Jeuris

Faites de MyStruct une classe (au lieu de struct) et vous pourrez le faire.

1
Adrian Carneiro

Je pense que vous pouvez trouver la réponse ci-dessous.

Erreur de compilation étrange de la structure Foreach en C #

1
Jethro