Reputation: 360
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);
}
}
Upvotes: 4
Views: 537
Reputation: 755387
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.
Upvotes: 4
Reputation: 81277
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:
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.
Upvotes: 1
Reputation: 34539
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.
Upvotes: 0