Foreach struct weird compile error in C#

2020-07-22 18:48发布

问题:

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.

回答1:

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

Why are mutable structs “evil”?



回答2:

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.



回答3:

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.



回答4:

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.



回答5:

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).