In C#, why can't I modify the member of a value type instance in a foreach loop?

I know that value types should be immutable, but that's just a suggestion, not a rule, right? So why can't I do something like this:

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

The foreach loop in the code doesn't compile while the commented for loop works fine. The error message:

Cannot modify members of 'item' because it is a 'foreach iteration variable'

Why is that?


Solution 1:

Because foreach uses an enumerator, and enumerators can't change the underlying collection, but can, however, change any objects referenced by an object in the collection. This is where Value and Reference-type semantics come into play.

On a reference type, that is, a class, all the collection is storing is a reference to an object. As such, it never actually touches any of the object's members, and couldn't care less about them. A change to the object won't touch the collection.

On the other hand, value types store their entire structure in the collection. You can't touch its members without changing the collection and invalidating the enumerator.

Moreover, the enumerator returns a copy of the value in the collection. In a ref-type, this means nothing. A copy of a reference will be the same reference, and you can change the referenced object in any way you want with the changes spreading out of scope. On a value-type, on the other hand, means all you get is a copy of the object, and thus any changes on said copy will never propagate.

Solution 2:

It's a suggestion in the sense that there's nothing stopping you from violating it, but it should really be afforded much more weight than "it's a suggestion". For instance, for the reasons you're seeing here.

Value types store the actual value in a variable, not a reference. That means that you have the value in your array, and you have a copy of that value in item, not a reference. If you were allowed to change the values in item, it would not be reflected on the value in the array since it's a completely new value. This is why it isn't allowed.

If you want to do this, you'll have to loop over the array by index and not use a temporary variable.

Solution 3:

Structures are value types.
Classes are reference types.

ForEach construct uses IEnumerator of IEnumerable data type elements. When that happens, then variables are read-only in the sense, that you can not modify them, and as you have value type, then you can not modify any value contained, as it shares same memory.

C# lang spec section 8.8.4 :

The iteration variable corresponds to a read-only local variable with a scope that extends over the embedded statement

To fix this use class insted of structure;

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

Edit: @CuiPengFei

If you use var implicit type, it's harder for the compiler to help you. If you would use MyStruct it would tell you in case of structure, that it is readonly. In case of classes the reference to the item is readonly, so you can not write item = null; inside loop, but you can change it's properties, that are mutable.

You can also use (if you like to use 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";
        }

Solution 4:

NOTE: As per Adam's comment, this isn't actually the correct answer/cause of the problem. It's still something worth bearing in mind though.

From MSDN. You can't modify the values when using the Enumerator, which is essentially what foreach is doing.

Enumerators can be used to read the data in the collection, but they cannot be used to modify the underlying collection.

An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and its behavior is undefined.