C#中可以"return by reference"哪些类型的对象

What kinds of objects can "return by reference" in C#

我正在阅读
至于“return by reference”,它说

There are two important restrictions on return by reference—both due to object lifetime: Object references shouldn’t be garbage collected while they’re still referenced, and they shouldn’t consume memory when they no longer have any references. To enforce these restrictions, you can only return the following from a reference-returning function:
• References to fields or array elements
• Other reference-returning properties or functions
• References that were passed in as parameters to the by-reference returning function

我做了一些实验,结果果然和书上说的一样。

namespace App
{
    public class App
    {
        static void Main()
        {

        }
        class Test
        {
            public int x;
        }
        ref int RefReturn()
        {
            int[] a = { 1, 2 };
            return ref a[0];
        }
        ref int RefReturn2()
        {
            Test t = new Test();
            return ref t.x;
        }
        ref int RefReturnError()
        {
            int a = 1;
            return ref a; //Error
        }
        ref Test RefReturnError2()
        {
            Test t = new Test();
            return ref t; //Error
        }
    }
}

这个我不是很明白
(书中的3种可以return引用,为什么是这3种?)
例如,我可以引用 return class 的字段,但不能引用 class.
(我的代码中的“ref int RefReturn2()”和“ref Test RefReturnError2()”)

我觉得应该是关于C#中的“对象生命周期”和“垃圾回收”的东西,我不太了解。

我也想知道使用return的典型情况,供参考
我觉得典型情况也能帮助理解。

局部变量与方法具有相同的生命周期,变量本身的内存位置在堆栈上。

所以 变量 int aTest t 都不存在于方法 returns.

之后

但是 a[0]t.x 存在于存储在堆内存中的对象内部。它们在方法 returns 时仍会存在,因为它们位于堆栈之外。

举个例子怎么样。为什么 use 要使用 ref local 或 ref return?定义链表时怎么样;

internal class Node<T>
    where T:class
{
    internal T Item;
    internal Node<T> Next;
}

public class LinkedList<T>
    where T : class
{
    private Node<T> Root;

    private ref Node<T> Find(Func<T, bool> comparison)
    {
        ref var ret = ref Root;
        while (ret!=null && comparison(ret.Item))
            ret = ref ret.Next;
        return ref ret;
    }

    public void Insert(T newItem, Func<T, bool> comparison)
    {
        ref var position = ref Find(comparison);
        position = new Node<T>
        {
            Item = newItem,
            Next = position.Next
        };
    }
}

找到 return 对 Node<T> 字段的引用,然后您可以将新节点分配给该字段。所以你可以处理分配给 Rootsomenode.Next 而不需要两个特殊情况。

托管引用 (ref) 规则的关键要点是: 托管引用 不得 指向局部变量或局部变量的一部分(在结构的情况下),因为引用可以比它指向的位置的寿命更长。它必须指向一个非堆栈位置。

让我们一个一个地看每个版本


        ref int RefReturn()
        {
            int[] a = { 1, 2 };
            return ref a[0];
        }

在上面的例子中,返回的引用指向数组的内部,它不是指向局部变量。数组的内部实际上是堆对象的一个​​字段。该数组将比函数的寿命更长。


        ref int RefReturn2()
        {
            Test t = new Test();
            return ref t.x;
        }

在这个中,Test 是一个引用类型,因此存在于堆中。引用指向 t 中包含的对象的字段 x,这也存在于堆中。 t 是局部变量这一事实并不重要,引用不指向 t.


        ref int RefReturnError()
        {
            int a = 1;
            return ref a; //Error
        }

在这种情况下,引用指向局部变量的实际位置,它存在于堆栈中,该位置将在函数结束时消失。

请注意,当结构的位置是局部变量时,引用结构的字段时会出现同样的问题。

        ref int RefReturnError1A()
        {
            MyStruct a = new MyStruct();
            return ref a.x; //Error
        }

        ref Test RefReturnError2()
        {
            Test t = new Test();
            return ref t; //Error
        }

在这一个中,虽然t是一个引用类型并且它本身指向一个堆对象,但是我们的引用并不指向t指向的那个对象。它指向 t 本身 的 位置,其中包含该对象引用。


请注意,由于不同的原因,不允许引用装箱结构:由于 C# 的拆箱规则,拆箱(逻辑上)会创建一个副本,因此您不能就地更改它。直接在 IL 中(或在 C++/CLI 中)编码,你可以完全可验证地做等同于:

        ref int RefReturnBox()
        {
            object a = (object)1;
            return ref (int)a;    // CS0445: Cannot modify the result of an unboxing conversion
        }