Reputation: 14414
Given the method
ref readonly State GetState();
where State
is a struct
, the following code accesses State.SimTime
without copying State
:
ref readonly var state = ref GetState();
var t = state.SimTime;
Meanwhile this code does copy State
:
var start = GetState();
var t = state.SimTime;
My question is whether the following copies State
:
var t = GetState().SimTime;
My hope is that it doesn't, since the result of GetState()
is a ref and never assigned, but I don't know for sure and don't know how I would test for it.
As a bonus, is there a way to inspect code staticcally to determine whether memory is copied?
Upvotes: 3
Views: 98
Reputation: 1063844
For a field? No. However, for completeness, we should note that fields should usually not be exposed on the public API surface, and it should usually be a property getter, which is still compatible with the code in your question:
ref readonly var state = ref GetState();
var t = state.SimTime;
Now things get more interesting!
If the property getter is known to be side-effect free, then this is fine, but otherwise there is a defensive copy here to protect the fact that state
is meant to be ref readonly
. Normally, property getters can do anything, so fetching .SimTime
is not by itself reliably side-effect free. But we can make it!
There are two ways:
struct
as readonly
readonly
For example;
readonly struct Foo
{
public int SimTime => // some code
}
readonly struct Bar
{
public readonly int SimTime => // some code
}
Note that nothing is ever truly readonly
, and while the compiler will enforce rules, code can still bypass those rules if you try hard enough.
Finally, note that in the case of automatically implemented properties (things like public int X {get; ...}
on struct
types, the get
is treated as readonly
automatically. This means that auto-props behave without an additional copy.
Upvotes: 1
Reputation: 273530
GetState().SimTime
does not create a copy of State
. You can see this by looking at the generated IL on SharpLab or some other tool.
GetState().SimTime
gets compiled to two call
instructions:
// IL pseudocode
call GetState
call get_SimTime
The code snippet that uses a ref readonly var
compiles to the same two call
instructions.
On the other hand, the code snippet that does not use ref readonly var
compiles to:
// IL pseudocode
call GetState
ldobj State
stloc.0
ldloca.s 0
call get_SimTime
The key instruction here is ldobj
- this is what turns the reference that GetState
returns, into an actual struct value (i.e. the copy of State
). If you don't see an instruction like ldobj
after calling a ref-return method, that means the code is operating on the reference returned by the method, and not a struct value.
As Marc Gravell's answer says, the runtime would create a defensive copy if the getter of SimTime
has side effects.
Upvotes: 2
Reputation: 4915
No, it won't be copied.
From docs:
To use a value returned from a method, the calling method can use the method call itself anywhere a value of the same type would be sufficient.
Using the GetValue()
method call is equivalent to using a ref
local variable/parameter, i.e. byRefParameter
.
If we didn't have the readonly
modifier we could easily demonstrate this by writing:
GetState().SimTime = 42; // byRefParameter.SimTime = 42
And since accessing a field of a byref
argument/local variable, doesn't involve copying the struct
var t = byRefParameter.SimTime;
the same holds for your case with
var t = GetState().SimTime;
Upvotes: 2