Consider the Foo struct as follows:
struct Foo
{
public float X;
public float Y;
public Foo(float x, float y)
{
this.X = x;
this.Y = y;
}
Structures in .net combine piecewise mutability with shallow-copy-on-assignment semantics as well as the ability to pass by value-assignment or by reference. There is no convention in .net, however, by which classes would be expected to expose properties by reference, nor do any .net language compilers provide a convenient means of doing so. It would be possible for a language to provide such a feature, with certain limitations, by recognizing that something like:
somePoint.X = 5;
could be written as:
void SetXToFive(ref Point it) {it.X = 5;} ... SetXToFive(ref somePoint);
allowing the code which manipulates the Point
(by setting its X
field to 5) from the code which has access to it. If an object which would have a property of type Point then exposes a routine which accepts a delegate to a method like the above, code that wants to set field X
of that property to 5 could pass that routine a delegate to SetXToFive
, which the routine could then call with whatever storage location holds the Point
in question.
Note that one advantage over such an approach, compared with simply exposing a reference to the thing to be manipulated, is that the owner of the Point
would know when the code that was manipulating it had finished. Without some compiler supper, the approach would generally be more of a nuisance than a benefit, but with compiler support the semantics could be made much cleaner than would be possible via any other means.
Since one cannot do
Foo bar = new Foo(1, 2);
bar.X = 5;
Why can one use:
Foo bar = new Foo(1, 2);
bar.Change(5);
Your original question actually cannot be answered because it is predicated on a completely false assumption. Both code samples are perfectly legal, and so the question about why one is illegal is nonsensical. Let's move on to your follow-up question:
If structs are mutable, then why can't they be modified when in a list or returned from a property?
Because variables are mutable and values are immutable.
That's why they're called "variables", after all, because they can change.
When you say "bar.X = 5", "bar" is a local variable. Variables are allowed to change.
When you say "bar.Change(5)", "bar" is a local variable. Variables are allowed to change.
When you say "myArray[123].X = 5", "myArray[123]" is an array element and an array element is a variable. Variables are allowed to change.
When you say "myDictionary[123].X = 5", "myDictionary[123]" is not a variable. The value is returned from the dictionary, not a reference to the storage location. Since that is a value, not a variable, there is nothing there that can change, so the compiler does not allow it to change.
A subtle point is that when you attempt to change a field, the receiver must be a variable. If it is not a variable, it makes no sense; you are clearly attempting to mutate a variable and there's nothing there to mutate. When you call a method, the receiver must be a variable but what if you have a value? The method might not attempt to mutate anything, and so should be allowed to succeed. What the compiler actually does if the receiver of a method call on a struct is not a variable, then it makes a new temporary local variable and calls the method with that variable. So if you say: "myDictionary[123].Change(5);" that is the same as saying
var temp = myDictionary[123];
temp.Change(5);
Here "temp" is a variable, and the mutating method is allowed to change the temporary copy.
Is that now clear? The key takeaway here is variables can change.
You've made a key mistaken assumption.
.NET structs are mutable. You can absolutely perform bar.X = 5;
.
You should design structs to be immutable, but by the code you have provided, they are mutable.
Have a look at this question for a description of where mutable structs can get your into trouble. Immutability of structs
Found my answer at the very bottom of: http://www.albahari.com/valuevsreftypes.aspx
However, I still don't understand why the compiler allows mutable structs in that case.
In common, all C# structs are not immutable, even readonly ones. So you can't design your structs as immutable at all.
All structs are mutable, just like in C++ :)
Immutability means that data structures ate immutable at language level, that is not true for C#. I will show you how to break immutability rule using legal C# syntax, please note that NotReallyImmutableFoo.X is declared as a readonly field.
Cheers ;)
namespace test
{
public unsafe struct MutableFoo
{
public int Id;
public float X;
public MutableFoo(int id, float x) { Id = id; X = x; }
public void Change(float x)
{
unsafe
{
fixed (MutableFoo* self = &(this))
{
MutabilityHelper.Rewrite(self, x);
}
}
}
}
public struct NotReallyImmutableFoo
{
public long Id;
public readonly float X;
public NotReallyImmutableFoo(long id, float x) { Id = id; X = x; }
public void Change(float x)
{
unsafe
{
fixed (NotReallyImmutableFoo* self = &(this))
{
MutabilityHelper.Rewrite(self, x);
}
}
}
}
// this calls breaks up the immutability rule, because we are modifying structures itself
public static class MutabilityHelper
{
struct MutableFooPrototype
{
int Id;
float X;
public void Rewrite(float value)
{
X = value;
}
}
struct NotReallyImmutableFooPrototype
{
long Id;
float X;
public void Rewrite(float value)
{
X = value;
}
}
public static unsafe void Rewrite(NotReallyImmutableFoo* obj, float value)
{
NotReallyImmutableFooPrototype* p_obj = (NotReallyImmutableFooPrototype*)(*(&obj));
p_obj->Rewrite(value);
}
public static unsafe void Rewrite(MutableFoo* obj, float value)
{
MutableFooPrototype* p_obj = (MutableFooPrototype*)(*(&obj));
p_obj->Rewrite(value);
}
}
class Program
{
static void Main(string[] args)
{
MutableFoo foo = new MutableFoo(0, 2);
foo.X = 3; // X is writeable
foo.Change(5); // write X using pointer prototyping
NotReallyImmutableFoo nrifoo = new NotReallyImmutableFoo(0, 2);
// error CS0191
//nrifoo.X = 3; // X is not writeable
nrifoo.Change(3); // anyway, write X using pointer prototyping
}
}
}