Reputation: 506
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?
Upvotes: 5
Views: 495
Reputation: 1062915
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.
Upvotes: 4
Reputation: 101483
I feel like you understand yourself already why it does not work. You cannot return local variable by reference from method (unless it's ref local), because in most cases lifetime of local variable is the method, so its reference outside of method does not have any meaning (outside of method this variable is dead and location where it were before might contain anything). As documentation states:
The return value must have a lifetime that extends beyond the execution of the method. In other words, it cannot be a local variable in the method that returns it
In practice some local variables might live longer than execution of method they are declared in. For example, variables captured by closure:
int myLocal = 5;
SomeMethodWhichAcceptsDelegate(() => DoStuff(myLocal));
return ref myLocal;
However, this introduces additional complications without any benefits, so this is also forbidden, even though lifetime of myLocal
might be much longer than containing method.
It's better to not think about it in terms of stack and heap. For example you might think that you cannot return reference to something allocated on stack from the method via ref return
. That's not true, for example:
private void Test() {
int myLocal = 4;
GetX(ref myLocal);
}
private ref int GetX(ref int i) {
return ref i;
}
Here myLocal
is clearly on stack, and we pass it by reference to GetX
and then return this (stack allocated) variable with return ref
.
So just think about it in terms of variable lifetimes and not stack\heap.
In your second example, lifetime of _myInt
field is clearly longer than execution of GetX
, so there is no problem to return it by reference.
Note also that whether you return value type or reference type with return ref
doesn't make any difference in context of this question.
Upvotes: 3