值类型变量 - 由ref返回 - 在哪里生效?堆栈还是堆?

时间:2018-03-19 08:13:25

标签: c# stack heap pass-by-reference c#-7.2

我最近听说过7.2中的新C#功能,所以我们现在可以返回值类型的引用(例如int),甚至是值类型的只读引用。所以据我所知,值类型存储在堆栈中。当剩下方法时,它们会从堆栈中删除。那么当方法GetX退出时,int会发生什么?

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?
}

我收到了错误

  

错误CS8168:无法通过引用返回本地'myInt',因为它不是ref本地(11,24)

那么为什么它不起作用?它不起作用只是因为变量无法移动到堆中吗?这是问题吗?如果它们不在堆栈中,我们只能通过引用返回值类型吗?我知道这是两个问题。

首先:ref返回的值类型变量在哪里?堆栈还是堆? (我猜在堆上但是为什么)?

第二:为什么堆栈上创建的值类型不能通过引用返回?

所以这可以编译:

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?
}

如果我理解你的评论是正确的,那是因为现在_myInt不在GetX方法的内部而且不会在堆栈中创建吗?

2 个答案:

答案 0 :(得分:4)

  

据我所知,值类型存储在堆栈中。

因此是你困惑的基础;这是非常不准确的简化。结构可以存在于堆栈中,但它们也可以存在:

  • 作为堆上对象的字段
  • 作为其他结构上的字段(等等)堆上对象的字段
  • 装在堆上(直接或通过以上任何一种方式)
  • in unmanaged memory

你是对的,但是:如果你将方法的ref return 传递给 in 一个方法,你将拥有违反了堆栈的完整性。这正是不允许 的原因。

ref int RefLocal()
{
    int i = 42;
    return ref i;
    // Error CS8168  Cannot return local 'i' by reference because it is not a ref local
}

某些情况,当编译器可以证明即使它存储为本地时,生命周期也是作用于此方法的;它有助于您无法重新分配ref本地(事实上,此检查是此限制的关键原因);这允许:

ref int RefParamViaLoval(ref int arg)
{
    ref int local = ref arg;
    return ref local;
}

由于ref int arg的生命周期不限于该方法,因此我们的ref int local可以在作业中继承此生命周期。

那么可以我们有用地返回什么?

它可以是数组内部的引用

ref int RefArray(int[] values)
{
    return ref values[42];
}

它可能是对象上的字段(不是属性):

ref int ObjFieldRef(MyClass obj)
{
    return ref obj.SomeField;
}

它可能是通过引用传入的struct 上的字段(不是属性):

ref int StructFieldRef(ref MyStruct obj)
{
    return ref obj.SomeField;
}

只要呼叫不涉及已知指向本地的的任何参考本地(这将使得无法证明其有效性),它可能是从正在进行的呼叫获得的东西:< / p>

ref int OnwardCallRef()
{
    ref MyStruct obj = ref GetMyStructRef();
    return ref obj.SomeField;
}

这里再次注意,本地的生命周期继承了传入前向调用的任何参数的生命周期;如果转发调用涉及ref - 本地且生命周期,则结果将继承该约束生命周期,并且您将无法返回它。

例如,那个向前调用可能会调用非托管内存中的结构:

ref int UnmanagedRef(int offset)
{
    return ref Unsafe.AsRef<int>(ptr + offset);
}

所以:许多非常有效和有用的场景不涉及对当前堆栈框架的引用。

答案 1 :(得分:3)

我觉得你已经明白了为什么它不起作用。你不能通过方法引用返回局部变量(除非它是ref本地),因为在大多数情况下局部变量的生命周期是方法,所以它在方法之外的引用没有任何意义(方法之外)这个变量是死的,它之前的位置可能包含任何东西)。正如文件所述:

  

返回值的生命周期必须超出   执行该方法。换句话说,它不能是局部变量   在返回它的方法

在实践中,一些局部变量可能比执行它们声明的方法更长寿。例如,闭包捕获的变量:

int myLocal = 5;
SomeMethodWhichAcceptsDelegate(() => DoStuff(myLocal));
return ref myLocal;

然而,这会带来额外的复杂性而没有任何好处,所以这也是禁止的,即使myLocal的生命周期可能比包含方法长得多。

最好不要在堆栈和堆方面考虑它。例如,您可能认为您无法通过ref return从方法返回对堆栈上分配的内容的引用。这不是真的,例如:

private void Test() {
    int myLocal = 4;
    GetX(ref myLocal);       
}

private ref int GetX(ref int i) {            
    return ref i;
}

这里myLocal显然在堆栈上,我们通过引用GetX传递它,然后用return ref返回这个(堆栈分配的)变量。

所以只考虑变量生命周期而不是堆栈\堆。

在您的第二个示例中,_myInt字段的生命周期明显长于GetX的执行时间,因此通过引用返回它是没有问题的。

另请注意,无论是使用return ref返回值类型还是引用类型,都不会对此问题的上下文产生任何影响。