Perhaps I'm misunderstanding the concept of a readonly struct, but I would think this code should not compile:
public readonly struct TwoPoints
{
private readonly Point one;
private readonly Point two;
void Foo()
{
// compiler error: Error CS1648 Members of readonly field 'TwoPoints.one'
// cannot be modified (except in a constructor or a variable initializer)
one.X = 5;
//no compiler error! (and one is not changed)
one.Offset(5, 5);
}
}
(I'm using C# 7.3.) Am I missing something?
There is no way for compiler to figure out that Offset
method mutates Point
struct members. However, readonly
struct field is handled differently compared to non-readonly one. Consider this (not readonly) struct:
public struct TwoPoints {
private readonly Point one;
private Point two;
public void Foo() {
one.Offset(5, 5);
Console.WriteLine(one.X); // 0
two.Offset(5, 5);
Console.WriteLine(two.X); // 5
}
}
one
field is readonly but two
is not. Now, when you call a method on readonly
struct field - a copy of struct is passed to that method as this
. And if method mutates struct members - than this copy members are mutated. For this reason you don't observe any changes to one
after method is called - it has not been changed but copy was.
two
field is not readonly, and struct itself (not copy) is passed to Offset
method, and so if method mutates members - you can observe them changed after method call.
So, this is allowed because it cannot break immutability contract of your readonly struct
. All fields of readonly struct
should be readonly
, and methods called on readonly struct field cannot mutate it, they can only mutate a copy.
If you are interested in "official source" for this - specification (7.6.4 Member access) says that:
If T is a struct-type and I identifies an instance field of that struct-type:
• If E is a value, or if the field is readonly and the reference occurs outside an instance constructor of the struct in which the field is declared, then the result is a value, namely the value of the field I in the struct instance given by E.
• Otherwise, the result is a variable, namely the field I in the struct instance given by E.
T
here is target type, and I
is member being accessed.
First part says that if we access instance readonly field of a struct, outside of constructor, the result is value. In second case, where instance field is not readonly - result is variable.
Then section "7.5.5 Function member invocation" says (E
here is instance expression, so for example one
and two
above):
• If M is an instance function member declared in a value-type:
If E is not classified as a variable, then a temporary local variable of E’s type is created and the value of E is assigned to that variable. E is then reclassified as a reference to that temporary local variable. The temporary variable is accessible as this within M, but not in any other way. Thus, only when E is a true variable is it possible for the caller to observe the changes that M makes to this.
As we saw above, readonly struct field access results in a value
, not a variable and so, E
is not classified as variable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With