В C #, почему я не могу изменить член экземпляра типа значения в цикле foreach? - PullRequest
59 голосов
/ 13 апреля 2011

Я знаю, что типы значений должны быть неизменяемыми, но это всего лишь предположение, а не правило, верно? Так почему я не могу сделать что-то вроде этого:

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();
    }
}

Цикл foreach в коде не компилируется, а закомментированный цикл работает нормально. Сообщение об ошибке:

Невозможно изменить элементы 'item', потому что это 'переменная итерации foreach'

Почему это?

Ответы [ 7 ]

63 голосов
/ 13 апреля 2011

Поскольку foreach использует перечислитель, и перечислители не могут изменить базовую коллекцию, но может , однако, можно изменить любые объекты , на которые ссылается объект в коллекции. Здесь вступают в игру семантика типа Value и Reference.

В ссылочном типе, то есть в классе, вся коллекция хранит ссылку на объект. Как таковой, он на самом деле никогда не касается ни одного из членов объекта и не может заботиться о них. Изменение объекта не затронет коллекцию.

С другой стороны, типы значений хранят всю свою структуру в коллекции. Вы не можете прикоснуться к его членам, не изменив коллекцию и не сделав недействительным счетчик.

Кроме того, перечислитель возвращает копию значения в коллекции. В ref-типе это ничего не значит. Копия ссылки будет той же самой ссылкой, и вы можете изменить ссылочный объект любым способом, каким захотите, с изменениями, выходящими за пределы области видимости. С другой стороны, значение типа означает, что все, что вы получаете, - это копия объекта, и поэтому любые изменения в этой копии никогда не будут распространяться.

16 голосов
/ 13 апреля 2011

Это предположение в том смысле, что ничто не мешает вам его нарушить, но на самом деле ему следует придать гораздо больший вес, чем «это предложение». Например, по причинам, которые вы видите здесь.

Типы значений хранят фактическое значение в переменной, а не в качестве ссылки. Это означает, что у вас есть значение в вашем массиве, и у вас есть копия этого значения в item, а не ссылка. Если вам разрешено изменять значения в item, это не будет отражаться на значении в массиве, так как это совершенно новое значение. Вот почему это не разрешено.

Если вы хотите сделать это, вам придется перебирать массив по индексу и не использовать временную переменную.

9 голосов
/ 13 апреля 2011

Структуры являются типами значений.
Классы являются ссылочными типами.

ForEach конструкция использует IEnumerator из IEnumerable элементы типа данных.Когда это происходит, переменные доступны только для чтения в том смысле, что вы не можете их изменять, и, поскольку у вас есть тип значения, вы не можете изменять любое содержащееся в нем значение, поскольку оно совместно использует одну и ту же память.

C #lang spec section 8.8.4:

Переменная итерации соответствует локальной переменной только для чтения с областью действия, которая распространяется на встроенный оператор

Для исправления этого класса использованиявместо структуры;

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

Редактировать: @ CuiPengFei

Если вы используете var неявный тип, компилятору будет труднее вам помочь.Если вы используете MyStruct, в случае структуры вам будет сказано, что он доступен только для чтения.В случае классов ссылка на элемент доступна только для чтения, поэтому вы не можете писать item = null; внутри цикла, но вы можете изменить его свойства, которые являются изменяемыми.

Вы также можете использовать (если вы хотите использовать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";
        }
3 голосов
/ 13 апреля 2011

ПРИМЕЧАНИЕ: Согласно комментарию Адама, на самом деле это не правильный ответ / причина проблемы. Это все же стоит помнить.

С MSDN . Вы не можете изменять значения при использовании Enumerator, что по сути и делает foreach .

Счетчики могут использоваться для чтения данные в сборе, но они не может быть использован для изменения Основная коллекция.

Перечислитель остается в силе до тех пор, пока коллекция остается без изменений. Если внесены изменения в коллекцию, такие как добавление, изменение или удаление элементы, перечислитель безвозвратно признан недействительным и его поведение не определено.

1 голос
/ 13 апреля 2011

Типы значений: , называемые значением .Короче говоря, когда вы оцениваете переменную, создается ее копия.Даже если бы это было разрешено, вы бы редактировали копию, а не исходный экземпляр MyStruct.

foreach только для чтения в C # .Для ссылочных типов (класса) это мало что меняет, так как только ссылка доступна только для чтения, поэтому вы все равно можете делать следующее:

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

Однако для типов значений (структура) весь объект целикомтолько для чтения, в результате чего вы испытываете.Вы не можете настроить объект в foreach.

1 голос
/ 13 апреля 2011

Я думаю, что вы можете найти ответ ниже.

Foreach struct странная ошибка компиляции в C #

1 голос
/ 13 апреля 2011

Сделайте MyStruct классом (вместо struct), и вы сможете это сделать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...