В 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 ответов


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

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

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

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


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

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

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


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

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

раздел 8.8.4 спецификации c# lang:

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

чтобы исправить это, используйте класс 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#. Для ссылочных типов (class) это не сильно меняется, так как только ссылка читается, поэтому вам все еще разрешено делать следующее:

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

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