I frequently read that struct
s should be immutable - aren't they by definition?
Do you consider int
to be immutable?
int i = 0;
i = i + 123;
Seems okay - we get a new int
and assign it back to i
. What about this?
i++;
Okay, we can think of it as a shortcut.
i = i + 1;
What about the struct
Point
?
Point p = new Point(1, 2);
p.Offset(3, 4);
Does this really mutate the point (1, 2)
? Shouldn't we think of it as a shortcut for the following with Point.Offset()
returning a new point?
p = p.Offset(3, 4);
The background of this thought is this - how can a value type with no identity be mutable? You have to look at it at least twice to determine if it changed. But how can you do this without an identity?
I don't want to complicate reasoning about this by considering ref
parameters and boxing. I am also aware that p = p.Offset(3, 4);
expresses immutability much better than p.Offset(3, 4);
does. But the question remains - aren't value types immutable by definition?
UPDATE
I think there are at least two concepts involved - the mutability of a variable or field and the mutability of the value of a variable.
public class Foo
{
private Point point;
private readonly Point readOnlyPoint;
public Foo()
{
this.point = new Point(1, 2);
this.readOnlyPoint = new Point(1, 2);
}
public void Bar()
{
this.point = new Point(1, 2);
this.readOnlyPoint = new Point(1, 2); // Does not compile.
this.point.Offset(3, 4); // Is now (4, 6).
this.readOnlyPoint.Offset(3, 4); // Is still (1, 2).
}
}
In the example we have to fields - a mutable one and a immutable one. Because a value type field contains the whole value, a value type stored in a immutable field must be immutable, too. I am still quite suprised by the result - I did not exspect the readonly field to remain unmodified.
Variables (besides constants) are allways mutable, hence they imply no restriction on the mutability of value types.
The answer seems not to be that straight forward so I will rephrase the question.
Given the following.
public struct Foo
{
public void DoStuff(whatEverArgumentsYouLike)
{
// Do what ever you like to do.
}
// Put in everything you like - fields, constants, methods, properties ...
}
Can you give a completed version of Foo
and an usage example - that may include ref
parameters and boxing - so that it is not possible to rewrite all occurences of
foo.DoStuff(whatEverArgumentsYouLike);
with
foo = foo.DoStuff(whatEverArgumentsYouLike);
An object is immutable if its state doesn’t change once the object has been created.
Short answer: No, value types are not immutable by definition. Both structs and classes can be either mutable or immutable. All four combinations are possible. If a struct or class has non-readonly public fields, public properties with setters, or methods which set private fields, it is mutable because you can change its state without creating a new instance of that type.
Long answer: First of all, the question of immutability only applies to structs or classes with fields or properties. The most basic types (numbers, strings, and null) are inherently immutable because there is nothing (field/property) to change about them. A 5 is a 5 is a 5. Any operation on the 5 only returns another immutable value.
You can create mutable structs such as System.Drawing.Point
. Both X
and Y
have setters which modify the struct's fields:
Point p = new Point(0, 0);
p.X = 5;
// we modify the struct through property setter X
// still the same Point instance, but its state has changed
// it's property X is now 5
Some people seem to confuse immutablity with the fact that value types are passed by value (hence their name) and not by reference.
void Main()
{
Point p1 = new Point(0, 0);
SetX(p1, 5);
Console.WriteLine(p1.ToString());
}
void SetX(Point p2, int value)
{
p2.X = value;
}
In this case Console.WriteLine()
writes "{X=0,Y=0}
". Here p1
was not modified because SetX()
modified p2
which is a copy of p1
. This happens because p1
is a value type, not because it is immutable (it isn't).
Why should value types be immutable? Lots of reasons... See this question. Mostly it's because mutable value types lead to all sorts of not-so-obvious bugs. In the above example the programmer might have expected p1
to be (5, 0)
after calling SetX()
. Or imagine sorting by a value which can later change. Then your sorted collection will no longer be sorted as expected. The same goes for dictionaries and hashes. The Fabulous Eric Lippert (blog) has written a whole series about immutability and why he believes it's the future of C#. Here's one of his examples that lets you "modify" a read-only variable.
UPDATE: your example with:
this.readOnlyPoint.Offset(3, 4); // Is still (1, 2).
is exactly the what Lippert referred to in his post about modifying read-only variables. Offset(3,4)
actually modified a Point
, but it was a copy of readOnlyPoint
, and it was never assigned to anything, so it's lost.
And that is why mutable value types are evil: They let you think you are modifying something, when sometimes you are actually modifying a copy, which leads to unexpected bugs. If Point
was immutable, Offset()
would have to return a new Point
, and you would not have been able to assign it to readOnlyPoint
. And then you go "Oh right, it's read-only for a reason. Why was I trying to change it? Good thing the compiler stopped me now."
UPDATE: About your rephrased request... I think I know what you're getting at. In a way, you can "think" of structs as being internally immutable, that modifying a struct is that same as replacing it with a modified copy. It might even be what the CLR does internally in memory, for all I know. (That's how flash memory works. You cannot edit just a few bytes, you need to read a whole block of kilobytes into memory, modify the few you want, and write the whole block back.) However, even if they were "internally immutable", that is an implementation detail and for us developers as users of structs (their interface or API, if you will), they can be changed. We can't ignore that fact and "think of them as immutable".
In a comment you said "you cannot have a reference to the value of field or variable". You are assuming that every struct variable has a different copy, such that modifying one copy does not affect the others. That is not entirely true. The lines marked below are not replaceable if...
interface IFoo { DoStuff(); }
struct Foo : IFoo { /* ... */ }
IFoo otherFoo = new Foo();
IFoo foo = otherFoo;
foo.DoStuff(whatEverArgumentsYouLike); // line #1
foo = foo.DoStuff(whatEverArgumentsYouLike); // line #2
Lines #1 and #2 do not have the same results... Why? Because foo
and otherFoo
refer to the same boxed instance of Foo. Whatever is changed in foo
in line #1 reflects in otherFoo
. Line #2 replaces foo
with a new value and does nothing to otherFoo
(assuming that DoStuff()
returns a new IFoo
instance and does not modify foo
itself).
Foo foo1 = new Foo(); // creates first instance
Foo foo2 = foo1; // create a copy (2nd instance)
IFoo foo3 = foo2; // no copy here! foo2 and foo3 refer to same instance
Modifying foo1
won't affect foo2
or foo3
. Modifying foo2
will reflect in foo3
, but not in foo1
. Modifying foo3
will reflect in foo2
but not in foo1
.
Confusing? Stick to immutable value types and you eliminate the urge of modifying any of them.
UPDATE: fixed typo in first code sample