В C#, почему я не могу изменить член экземпляра типа значения в цикле foreach?


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

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 в коде не компилируется, пока цикл commented for работает нормально. Сообщение об ошибке:

не удается изменить члены "item", потому что это "переменная итерации foreach"

почему это?

7 56

7 ответов:

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

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

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

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

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

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

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

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

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

C# lang spec раздел 8.8.4:

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

чтобы исправить это, используйте класс insted структуры;

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

Edit: @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";
        }

Примечание:согласно комментарию Адама, это на самом деле не правильный ответ/причина проблемы. Это еще что-то стоит иметь в виду.

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

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

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

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

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

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

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

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

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

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