Dans C#, pourquoi ne puis-je pas modifier le membre d'une instance de type de valeur dans une boucle foreach?
je sais que les types de valeurs devraient être immuables, mais c'est juste une suggestion, pas une règle, pas vrai? 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 alors que la boucle commentée fonctionne bien. Le message d'erreur:
ne peut pas modifier les membres de "item" parce qu'il s'agit d'une "variable d'itération pour chaque itération "
pourquoi?
7 réponses
parce que foreach utilise un recenseur, et les recenseurs ne peuvent pas changer la collection sous-jacente, mais peut , cependant, changer tout objet référencé par un objet dans la collection. C'est là que la sémantique de la valeur et du type de 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 jamais réellement aucun des membres de l'objet, et ne pouvais pas moins de soins sur eux. Un changement à l'objet ne touchera pas la collection.
d'autre part, les types de valeur stockent leur structure entière dans la collection. Vous ne pouvez pas toucher ses membres sans changer la collection et invalider le recenseur.
de plus, le recenseur renvoie une copie de la valeur de 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 peut changer l'objet référencé de n'importe quelle façon que vous voulez avec les changements se propageant hors de portée. Sur un type de valeur, d'autre part, signifie que tout ce que vous obtenez est une copie de l'objet, et donc tout changement sur ladite copie ne se propagera jamais.
c'est une suggestion dans le sens où il n'y a rien qui vous empêche de la violer, mais il faut 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 dans une référence. Cela signifie que vous avez la valeur de votre tableau, et vous avez un copie de cette valeur dans item
, pas une référence. Si vous étiez autorisé à changer le valeurs dans item
, il ne serait pas réfléchi sur la valeur dans le tableau puisque c'est une valeur complètement nouvelle. C'est pourquoi il n'est pas autorisé.
si vous voulez faire cela, vous devrez boucler le tableau par index et ne pas utiliser de variable temporaire.
sont des types de valeurs.
Les Classes sont des types de référence.
ForEach
construit utilise IEnumerator de IEnumerable éléments de type de données. Lorsque cela se produit, alors les variables sont en lecture seule dans le sens que vous ne pouvez pas les modifier, et comme vous avez le type de valeur, alors vous ne pouvez pas modifier n'importe quelle 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 avec une portée qui s'étend au-delà de l'énoncé incorporé
pour la fixation de cette classe d'utilisation insté de structure;
class MyStruct {
public string Name { get; set; }
}
Edit: @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 la structure, qu'il est en lecture seule. Dans le cas de classes la référence à l'élément est en lecture seule, de sorte que vous ne pouvez pas écrire item = null;
boucle intérieure, 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";
}
NOTE: selon le commentaire D'Adam, ce n'est pas la bonne réponse/cause du problème. C'est encore quelque chose qui en vaille la peine, même si.
à Partir de MSDN . Vous ne pouvez pas modifier les valeurs en utilisant L'énumérateur, ce qui est essentiellement ce que foreach fait.
Les recenseurspeuvent être utilisés pour lire les données dans le collection, mais ils ne peut pas être utilisé pour modifier les la collection sous-jacente.
un recenseur reste valide aussi longtemps que la collection reste inchangée. Si des modifications sont apportées à la collecte, telles que l'ajout, la modification ou la suppression éléments, le recenseur est irrémédiablement invalidé et son le comportement est indéfini.
Faire MyStruct une classe (au lieu de struct) et vous serez en mesure de le faire.
sont appelés par la valeur . En bref, lorsque vous évaluez la variable, une copie en est faite. Même si cela était autorisé, vous éditeriez une copie, plutôt que l'instance originale 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, donc vous êtes encore autorisé à faire ce qui suit:
MyClass[] array = new MyClass[] {
new MyClass { Name = "1" },
new MyClass { Name = "2" }
};
foreach ( var item in array )
{
item.Name = "3";
}
pour les types de valeurs cependant (struct), l'objet entier est en lecture seule, résultant en ce que vous expérimentez. Vous ne pouvez pas ajuster l'objet dans l'antéach.