English 中文(简体)
Changing a struct inside another struct in a foreach loop
原标题:

The following code prints (when invoking MyMethod):

0
0
0
1

I would expect it to print:

0
0
1
1

Why is this?

Code:

private struct MyStruct
{
    public MyInnerStruct innerStruct;
}

private struct MyInnerStruct
{
    public int counter;

    public void AddOne()
    {
        ++counter;
    }
}

public static void MyMethod()
{
    MyStruct[] myStructs = new MyStruct[] { new MyStruct() };

    foreach (var myStruct in myStructs)
    {
        MyStruct myStructCopy = myStruct;

        Console.WriteLine(myStruct.innerStruct.counter);
        Console.WriteLine(myStructCopy.innerStruct.counter);

        myStruct.innerStruct.AddOne();
        myStructCopy.innerStruct.AddOne();

        Console.WriteLine(myStruct.innerStruct.counter);
        Console.WriteLine(myStructCopy.innerStruct.counter);
    }
}
最佳回答

The reason you are seeing this behavior has to due with using an iteration variable. Iteration variables are read-only in the sense that in C# you cannot modify them (C# lang spec section 8.8.4 details this

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

Playing with read-only mutable structs is a path to unexpected behavior. Instead of using the variable directly you are actually using a copy of the variable. Hence it s the copy that is getting incremented in the case of myStruct and not the actual value. This is why the original value remains unchanged.

Eric did a rather in depth article on this topic that you can access here

Yet another reason why you should always have immutable structs.

问题回答

Iterations variables are read-only, and one would normally be forbidden from modifying a read-only structure directly, or passing such a structure or any part thereof by ref to any code which would modify it. Unfortunately, there s a wrinkle: even though many struct methods and properties don t mutate the underlying structure, calling a method or property on a structure requires that it be passed by ref whether or not the method or property will alter the struct instance. This left the designers of C# with five choices:

  1. Don t allow struct methods or properties to be used on structures in read-only context without explicitly copying the structs to a mutable storage location (most likely a local variable).
  2. Allow struct methods or properties to be used on structures in read-only context, and hope that they won t modify anything they re not supposed to.
  3. When calling a method or a property on a read-only struct, silently make a copy of the struct and give that copy to the method or property.
  4. Provide a means by which struct methods or properties can indicate declaratively whether or not they will mutate the underlying structure; only allow struct methods and properties to modify their own fields if they are declared as doing so, and only allow read-only structs to be passed to methods which are not declared as modifying them.
  5. Provide a means by which struct methods or properties can indicate declaratively whether or not they will mutate the underlying structure; forbid the use of such methods and properties on structures in read-only contexts, but make a defensive copy of read-only structures when calling methods without such declarations just in case the methods should have them but don t.

Choice #1 would be a little annoying, especially with regard to structs that expose their state via properties. Choice #2 or #4 would be good from a performance standpoint, but struct routines which unexpectedly write this could cause unexpected behavior. Microsoft opted for #3 (choice #5 would be the same except for the addition of optional attributes). In many ways, that s the worst choice (methods which don t write this will run slower than they would without the copy operation, methods which do write this won t work correctly with or without it, and the only thing damaged when a struct writes a read-only variable would be an instance of a struct whose code was already broken). Nonetheless, that choice is by now well established, and it is what it is.

Incidentally, even read operations structures nested within readonly structures or properties of structure type can be much more expensive than using non-read-only fields but simply refraining from writing them. When calling myStruct.innerStruct.AddOne(), the compiler first makes a copy of MyStruct, and then makes a copy of myStruct.InnerStruct, and then calls the AddOne() method on that. By contrast, when calling myStructCopy.innerStruct.AddOne(), it can pass innerStruct by reference to the AddOne() method without copying anything.

I think you ll find that the MyStruct.innerStruct actually returns a copy of the struct, and not the struct itself. Therefore you re increasing the value of the copy...

Sure I read a blog about this recently somewhere.

If you change the following

myStruct.innerStruct.AddOne();
Console.WriteLine(myStruct.innerStruct.counter);

to

MyInnerStruct inner = myStruct.innerStruct;
inner.AddOne();
Console.WriteLine(inner.counter);

Then you should see it start working.





相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...

热门标签