I recently heard about the new C# Feature in 7.2, so that we now can return a reference of value type (for example int
) or even a readonly reference of a value type. So as far as I know a value type is stored in the stack. And when the method is left, they are removed from stack. So what happens with the int when the method GetX
exits?
private ref int GetX()
{
// myInt is living on the stack now right?
int myInt = 5;
return ref myInt;
}
private void CallGetX()
{
ref int returnedReference = ref GetX();
// where does the target of 'returnedReference' live now?
// Is it somehow moved to the heap, because the stack of 'GetX' was removed right?
}
I'm getting the error
Error CS8168: Cannot return local 'myInt' by reference because it is not a ref local (11, 24)
So why does it not work? Does it not work just because the variable can not be moved to the heap? Is this the problem? can we only return value types by reference if they do not live in the stack? I know that this are two question in one.
First: Where do value type-variables returned by ref live? Stack or heap? (I guess on the heap but why)?
Second: Why can a value type created on the stack not be returned by reference?
So this is able to be compiled:
private int _myInt;
private ref int GetX()
{
// myInt is living on the stack now right?
_myInt = 5;
return ref _myInt;
}
private void CallGetX()
{
ref int returnedReference = ref GetX();
// where does the target of 'returnedReference' live now?
// Is it somehow moved to the heap? becase the stack of 'GetX' was removed right?
}
If I understand your comments right it is because now the _myInt lives not inside the method GetX
and there fore is not created in the stack right?
Reference TypesA reference type variable contains a reference to data stored in memory, also known as the heap. The heap is most often used for data that has a longer life. You can have more than one variable point to the same referenced data. Objects are an example of a reference type.
The return value is stored in registers - on x64 it goes in RAX.
Note that a value type can be stored in a stack frame, in the CPU register or even in the heap memory if the value type is contained inside an object, i.e., if it is a part of a reference type.
Reference Type variables are stored in the heap while Value Type variables are stored in the stack. Value Type: A Value Type stores its contents in memory allocated on the stack. When you created a Value Type, a single space in memory is allocated to store the value and that variable directly holds a value.
So as far as I know a value type is stored in the stack.
and thus is the basis of your confusion; this is a simplification that is grossly inaccurate. Structs can live on the stack, but they can also live:
You're right, though: if you passed a ref return
out of a method, to a local inside a method, you will have violated stack integrity. That's precisely why that scenario isn't allowed:
ref int RefLocal()
{
int i = 42;
return ref i;
// Error CS8168 Cannot return local 'i' by reference because it is not a ref local
}
There are some scenarios when the compiler can prove that even though it was stored as a local, the lifetime was was scoped to this method; it helps that you can't reassign a ref
local (to be honest, this check is a key reason for this restriction); this allows:
ref int RefParamViaLoval(ref int arg)
{
ref int local = ref arg;
return ref local;
}
Since ref int arg
has lifetime that isn't scoped to the method, our ref int local
can inherit this lifetime in the assignment.
So what can we usefully return?
It could be a reference to the interior of an array:
ref int RefArray(int[] values)
{
return ref values[42];
}
It could be a field (not property) on an object:
ref int ObjFieldRef(MyClass obj)
{
return ref obj.SomeField;
}
It could be a field (not property) on a struct passed in by reference:
ref int StructFieldRef(ref MyStruct obj)
{
return ref obj.SomeField;
}
It could be something obtained from an onward call as long as the call doesn't involve any ref locals known to point to locals (which would make it impossible to prove validity):
ref int OnwardCallRef()
{
ref MyStruct obj = ref GetMyStructRef();
return ref obj.SomeField;
}
Here again note that the lifetime of the local inherits the lifetime of any parameters passed into the onward call; if the onward call involved a ref
-local with constrained lifetime, then the result would inherit that constrained lifetime, and you would not be able to return it.
And that onward call could be, for example, calling out to structs held in unmanaged memory:
ref int UnmanagedRef(int offset)
{
return ref Unsafe.AsRef<int>(ptr + offset);
}
So: lots of very valid and useful scenarios that don't involve references to the current stack-frame.
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