I was reading documentation on C# 7.2 here and I came across this in regards to ref readonly
:
The compiler enforces that the caller can't modify the reference. Attempts to assign the value directly generate a compile-time error. However, the compiler can't know if any member method modifies the state of the struct. To ensure that the object isn't modified, the compiler creates a copy and calls member references using that copy. Any modifications are to that defensive copy.
This generated some confusion for me (and possibly some others), so I'd like to clarify the behavior now. Suppose I have a struct defined like this:
public struct Point3D
{
private static Point3D origin = new Point3D(0,0,0);
public static ref readonly Point3D Origin => ref origin;
public int X { get; set; }
public int Y { get; set; }
public int Z { get; set; }
public static void ChangeOrigin(int x = 0, int y = 0, int z = 0)
{
origin = new Point3D(x, y, z);
}
}
Now, suppose I used ref readonly
to get Point3D.Origin
and modified it:
ref readonly var origin = ref Point3D.Origin;
var originValue = Point3D.Origin;
Point3D.ChangeOrigin(1, 1, 1);
Console.WriteLine("Origin is: ({0}, {1}, {2})", origin.X, origin.Y, origin.Z);
Console.WriteLine("Origin is: ({0}, {1}, {2})", originValue.X, originValue.Y, originValue.Z);
The result from running this code is:
Origin is: (1, 1, 1)
Origin is: (0, 0, 0)
This is expected. The value in origin
is updated when I call ChangeOrigin
whereas the value in originValue
is copied and therefore doesn't change. My question is in regards to the "defensive copy" mentioned above. Why is this necessary? The value in origin
can't be changed without invoking compiler errors and the reference is updated properly when Point3D.Origin
updates so what reason is there to have an extra copy of the object, which from what I gathered by reading the documentation, isn't updated?
You can assign a value to a readonly
field only in the following contexts:
When the variable is initialized in the declaration.
C# example:
public readonly int y = 5;
In an instance constructor of the class that contains the instance field declaration.
In the static constructor of the class that contains the static field declaration.
These constructor contexts are also the only contexts in which it is valid to pass a readonly
field as an out
or ref
parameter.
If you use a statement like the following example:
p2.y = 66; // Error
you will get the compiler error message:
A readonly field cannot be assigned to (except in a constructor or a variable initializer)
The readonly
modifier on a ref
return indicates that the returned reference cannot be modified. The following example returns a reference to the origin. It uses the readonly
modifier to indicate that callers cannot modify the origin:
private static readonly Point origin = new Point(0, 0);
public static ref readonly Point Origin => ref origin;
The type returned doesn't need to be a readonly struct
. Any type that can be returned by ref
can be returned by ref readonly
.
This is all straight from the DOCs, and I hope it clarifies it for you! Happy coding!
So if you set (1, 1, 1)
in the constructer, it will be (1, 1, 1)
calling a change with a different method, as above, you get (0, 0, 0)