3
namespace MyNamespace
{
    public struct MyStruct
    {
        public string MyString;
        public int MyInt;
        public bool MyBool;
    }

    public class MyClass
    {
        private List<MyStruct> MyPrivateVariable;

        public List<MyStruct> MyVariable
        {
            get
            {
                if (MyPrivateVariable == null)
                {
                    MyPrivateVariable = new List<MyStruct>();

                    MyPrivateVariable.Add(new MyStruct());
                    MyPrivateVariable.Add(new MyStruct());
                }

                return MyPrivateVariable;
            }
        }

        public void MyLoop()
        {
            foreach (MyStruct ms in MyVariable)
            {
                // Doesn't compile, but it works if you execute it through the Immediate window, or in Quickwatch
                ms.MyBool = false;

                // Compiles, works
                MyFunction(ms);
            }
        }

        public void MyFunction(MyStruct ms)
        {
            ms.MyBool = false;
        }
    }
}

Any reasonable explanations for this?

The compiler returns:

Error: Cannot modify members of 'ms' because it is 'foreach iteration variable'

EDIT:

Extra question:

I just tried changing a string from MyFunction, and it doesn't actually update ms. BUT: If I go to quickwatch and assign the same value there, it does update ms. Why does this happen if it shouldn't even be compiling in the first place, shouldn't quickwatch throw an exception?

EDIT2:

Ok, quick watch also works on a copy of ms, so that's why I can edit it's value, it doesn't actually alter the contents of MyPrivateVariable.

BogadoDiego
  • 329
  • 3
  • 7
bevacqua
  • 47,502
  • 56
  • 171
  • 285

5 Answers5

14

You're using them as mutable structs. Avoid doing that:

Why are mutable structs “evil”?

Community
  • 1
  • 1
Joel Etherton
  • 37,325
  • 10
  • 89
  • 104
6

Struct has value type semantics. So any modification you make to the struct instance wouldn't affect the original instance. The C# compiler is trying to warn you of this.

Rohith
  • 2,043
  • 1
  • 17
  • 42
5

C# doesn't iterate structs by reference in the "foreach (MyStruct ms...)" so ms in that context is immutable.

Replace MyStruct with a class instead.

QuickWatch can manipulate value types on the stack.

burkestar
  • 753
  • 1
  • 4
  • 12
  • Sometimes it can be useful to wrap a mutable struct in a simple exposed-field `Holder` class for use in certain situations, but simply replacing a mutable struct with a mutable class to facilitate mutation of things in collections is a bad idea. In general, each mutable object should have exactly one other object which encapsulates its mutable state. If one wants to copy something from one `List>` to an existing spot in another, `List2[1].Value = List1[3].Value;`. To append one list item to another list, say `List2.Add(new Holder(List1[3]))`. – supercat Sep 17 '12 at 23:56
1

this is because struct is valuetype and not a reference type. if MyStruct was a class it would have compiled without issues. check this thread for details.

Community
  • 1
  • 1
Vinay B R
  • 8,089
  • 2
  • 30
  • 45
0

You can't change what an iteration variable references: that is, you can't point the variable to a different instance (to find out why that is, see Why is The Iteration Variable in a C# foreach statement read-only?).

'Modifying' a struct (a value type) creates a new instance of the type, so the statement ms.MyBool = false is meaningless.

The call to MyFunction(ms) compiles because it operates on a copy of ms (though it still won't do what you might expect).

Community
  • 1
  • 1
Jeff Sternal
  • 47,787
  • 8
  • 93
  • 120