Reputation: 1312
I have a Struct with a field in it that loses its value. I can declare the field static and that solves the problem. I can also just change struct to class (changing nothing else) and that also solves the problem. I was just wondering why this is?
Upvotes: 1
Views: 1575
Reputation: 310792
Structs are passed by value. In other words, when you pass a struct, you're passing a copy of its value. So if you take a copy of the value and change it, then the original will appear unchanged. You changed the copy, not the original.
Without seeing your code I cannot be sure, but I figure this is what's happening.
This doesn't happen for classes as they're passed by reference.
It's worth mentioning that this is why structs should be immutable -- that is, that once they're created, they do not change their value. Operations that provide modified versions return new structs.
EDIT: In the comments below, @supercat suggests that mutable properties can be more convenient. However property setters on structs can cause weird failures too. Here's an example that can catch you by surprise unless you deeply understand how structs work. For me, it's reason enough to avoid mutable structs altogether.
Consider the following types:
struct Rectangle {
public double Left { get; set; }
}
class Shape {
public Rectangle Bounds { get; private set; }
}
Ok, now imagine this code:
myShape.Bounds.Left = 100;
Perhaps surprisingly, This has no effect at all! Why? Let's re-write the code in longer yet equivalent form:
var bounds = myShape.Bounds;
bounds.Left = 100;
It's easier to see here how the value of Bounds
is copied to a local variable, and then its value is changed. However at no point is the original value in Shape
updated.
This is pretty compelling evidence to make all public structs immutable. If you know what you're doing, mutable structs can be handy, but personally I only really use them in that form as private nested classes.
As @supercat points out, the alternative is a little unsightly:
myShape.Bounds = new Rectangle(100, myShape.Bounds.Top,
myShape.Bounds.Width, myShape.Bounds.Height);
Sometimes it's more convenient to add helper methods:
myShape.Bounds = myShape.Bounds.WithLeft(100);
Upvotes: 5
Reputation: 81115
When a struct is passed by value, the system will make a copy of the struct for the callee, so it can see its contents, and perhaps modify its own copy, but but cannot affect the fields in the caller's copy. It's also possible to pass structs by ref
, in which case the callee will be able to work with the caller's copy of the struct, modifying it if desired, and even pass it by ref
to other functions which could do likewise. Note that the only way the called function can make the caller's copy of the struct available to other functions, though, is to pass it by ref
, and the called function can't return until all functions to which it has passed the struct by ref
have also returned. Thus, the caller can be assured that any changes which might occur to the structure as a consequence of the function call will have occurred by the time it returns.
This behavior is different from class objects; if a function passes a mutable class object to another function, it has no way of knowing if or when that other function will cause that object to be mutated immediately or at any future time, even after the function has finished running. The only way one can ever be sure that any mutable object won't be mutated by outside code is to be the sole holder of that object from the moment of its creation until its abandonment.
While one who is not used to value semantics may initially be "surprised" at the fact passing a struct by value simply gives the called function a copy of it, and assigning one struct storage location to another simply copies the contents of the struct, the guarantees that value types offer can be very useful. Since Point
is a structure, one can know that a statement like MyPoints[5].X += 1;
(assuming MyPoints
is an array) will affect MyPoints[5].X
but will not affect any other Point
. One can further be assured that the only way MyPoints[5].X
will change is if either MyPoints
gets replaced with another array, or something writes to MyPoints[5]
. By contrast, Point
were a class and MyPoint[5]
had ever been exposed to the outside world, the only way of knowing whether the aforementioned statement would affect field/property X
of any other storage locations of type Point
would be to examine every single storage location of type Point
or Object
that existed anywhere within the code to see if it pointed to the same instance as MyPoints[5]
. Since there's no way for code to examine all of the storage locations of a particular type, such assurance would be impossible if Point[5]
had ever been exposed to the outside world.
There is one annoying wrinkle with structs, though: generally, the system will only allow structures to be passed by ref
if the called code is allowed to write to the structure in question. Struct method calls and property getters, however, receive this
as a ref
parameter but do not have the above restriction. Instead, when invoking a struct method or property getter on a read-only structure, the system will make a copy of the structure, pass that copy by ref
to the method or property getter, and then discard it. Since the system has no way of knowing whether a method or property getter will try to mutate this
, it won't complain in such cases--it will just generate silly code. If one avoids mutating this
in anything other than property setters (the system won't allow the use of property setters on read-only structures), however, one can avoid problems.
Upvotes: 0