Подтвердить что ты не робот

В С#, почему я не могу изменить член экземпляра типа значения в цикле 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 работает нормально. Почему это?

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

Конструкция

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

С# lg 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";
        }

Ответ 4

ПРИМЕЧАНИЕ.. В соответствии с комментарием Адама это не является правильным ответом/причиной проблемы. Тем не менее, это все равно стоит иметь в виду.

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

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

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

Ответ 5

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

Ответ 7

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

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

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

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