I frequently read that struct
s should be immutable - aren\'t they by definition?
Do you consider int
to be immutable?
int i
No, value types are not immutable by definition.
First, I should better have asked the question "Do value types behave like immutable types?" instead of asking if they are immutable - I assume this caused a lot of confusion.
struct MutableStruct
{
private int state;
public MutableStruct(int state) { this.state = state; }
public void ChangeState() { this.state++; }
}
struct ImmutableStruct
{
private readonly int state;
public MutableStruct(int state) { this.state = state; }
public ImmutableStruct ChangeState()
{
return new ImmutableStruct(this.state + 1);
}
}
[To be continued...]
Last year I wrote a blog post regarding the problems you can run into by not making structs immutable.
The full post can be read here
This is an example of how things can go horribly wrong:
//Struct declaration:
struct MyStruct
{
public int Value = 0;
public void Update(int i) { Value = i; }
}
Code sample:
MyStruct[] list = new MyStruct[5];
for (int i=0;i<5;i++)
Console.Write(list[i].Value + " ");
Console.WriteLine();
for (int i=0;i<5;i++)
list[i].Update(i+1);
for (int i=0;i<5;i++)
Console.Write(list[i].Value + " ");
Console.WriteLine();
The output of this code is:
0 0 0 0 0
1 2 3 4 5
Now let's do the same, but substitute the array for a generic List<>
:
List<MyStruct> list = new List<MyStruct>(new MyStruct[5]);
for (int i=0;i<5;i++)
Console.Write(list[i].Value + " ");
Console.WriteLine();
for (int i=0;i<5;i++)
list[i].Update(i+1);
for (int i=0;i<5;i++)
Console.Write(list[i].Value + " ");
Console.WriteLine();
The output is:
0 0 0 0 0
0 0 0 0 0
The explanation is very simple. No, it's not boxing/unboxing...
When accessing elements from an array, the runtime will get the array elements directly, so the Update() method works on the array item itself. This means that the structs itself in the array are updated.
In the second example, we used a generic List<>
. What happens when we access a specific element? Well, the indexer property is called, which is a method. Value types are always copied when returned by a method, so this is exactly what happens: the list's indexer method retrieves the struct from an internal array and returns it to the caller. Because it concerns a value type, a copy will be made, and the Update() method will be called on the copy, which of course has no effect on the list's original items.
In other words, always make sure your structs are immutable, because you are never sure when a copy will be made. Most of the time it is obvious, but in some cases it can really surprise you...
You can write structs that are mutable, but it is best practice to make value types immutable.
For instance DateTime always creates new instances when doing any operation. Point is mutable and can be changed.
To answer your question: No, they are not immutable by definition, it depends on the case if they should be mutable or not. For instance, if they should serve as dictionary keys, they should be immutable.
aren't value types immutable by definition?
No they're not: if you look at the System.Drawing.Point
struct for example, it has a setter as well as a getter on its X
property.
However it may be true to say that all value types should be defined with immutable APIs.
To define whether a type is mutable or immutable, one must define what that "type" is referring to. When a storage location of reference type is declared, the declaration merely allocates space to hold a reference to an object stored elsewhere; the declaration does not create the actual object in question. Nonetheless, in most contexts where one talks about particular reference types, one will not be talking about a storage location which holds a reference, but rather the object identified by that reference. The fact that one can write to a storage location holding a reference to an object implies in no way that the object itself is mutable.
By contrast, when a storage location of value type is declared, the system will allocate within that storage location nested storage locations for each public or private field held by that value type. Everything about the value type is held in that storage location. If one defines a variable foo
of type Point
and its two fields, X
and Y
, hold 3 and 6 respectively. If one defines the "instance" of Point
in foo
as being the pair of fields, that instance will be mutable if and only if foo
is mutable. If one defines an instance of Point
as being the values held in those fields (e.g. "3,6"), then such an instance is by definition immutable, since changing one of those fields would cause Point
to hold a different instance.
I think it is more helpful to think of a value type "instance" as being the fields, rather than the values they hold. By that definition, any value type stored in a mutable storage location, and for which any non-default value exists, will always be mutable, regardless of how it is declared. A statement MyPoint = new Point(5,8)
constructs a new instance of Point
, with fields X=5
and Y=8
, and then mutates MyPoint
by replacing the values in its fields with those of the newly-created Point
. Even if a struct provides no way to modify any of its fields outside its constructor, there is no way a struct type can protect an instance from having all of its fields overwritten with the content of another instance.
Incidentally, a simple example where a mutable struct can achieve semantics not achievable via other means: Assuming myPoints[]
is a single-element array which is accessible to multiple threads, have twenty threads simultaneously execute the code:
Threading.Interlocked.Increment(myPoints[0].X);
If myPoints[0].X
starts out equal to zero and twenty threads perform the above code, whether simultaneously or not, myPoints[0].X
will equal twenty. If one were to attempt to mimic the above code with:
myPoints[0] = new Point(myPoints[0].X + 1, myPoints[0].Y);
then if any thread read myPoints[0].X
between the time another thread read it and wrote back the revised value, the results of the increment would be lost (with the consequence that myPoints[0].X
could arbitrarily end up with any value between 1 and 20.
Mutability and value types are two separate things.
Defining a type as a value type, indicates that the runtime will copy the values instead of a reference to the runtime. Mutability, on the other hand, depends on the implementation, and each class can implement it as it wants.