为什么结构不能包含可空的循环引用?

时间:2011-11-15 19:13:58

标签: c# .net struct circular-reference

我理解为什么结构不能包含导致逻辑内存问题的循环引用,但为什么可空引用不能绕过这个限制呢?例如:

struct Foo
{
    Foo? bar;
}

显然,这很容易导致堆栈溢出和循环引用,如果不小心,但不应该bar是指向另一个Foo实例的指针,并且默认为{{1 }}?或者(更有可能)我不明白在内存中如何排列可以为空的值类型?

(我的背景知识主要包括来自this question and answers的信息。)

6 个答案:

答案 0 :(得分:8)

不,不完全。可空值类型实际上是Nullable<>的实例,其值类型为通用参数。问号只是一种简写。

Nullable是一个结构,因此是一个值类型。由于它保留了对Foo结构的引用,因此您仍然有一个由值类型组成的循环引用。

答案 1 :(得分:6)

Nullable<T>是一个看起来像这样的结构(不包括构造函数等):

public struct Nullable<T> where T : struct
{
    private readonly T value;
    private readonly bool hasValue;
}

由于这是类型,您的Foo最终会看起来像这样:

struct Foo
{
    Foo barValue;
    bool hasBarValue;
}

现在希望更明显的是这是一个问题:)

答案 2 :(得分:5)

Foo? bar

的快捷方式
Nullable<Foo> bar;

Nullable<T>是一个大致如下所示的结构:

public struct Nullable<T> where T : struct
{
    private readonly T value;
    private readonly bool hasValue;
    //..
}

Foo的情况下,Nullable<Foo>会有一个Foo,而Nullable<Foo>依次拥有{{1}} ......

答案 3 :(得分:2)

正如您可能已经意识到的那样,结构体不能具有循环引用,因为当您将结构放在内存中时,您必须在结构中为每个成员包含存储。循环定义需要无限量的存储空间:

  • 具有两个Int32成员的结构需要8个字节(2 * sizeof(Int32));类似地,具有四个Int32成员的结构需要16个字节。
  • 如果结构S有两个Int32成员加一个S成员,则需要2 * sizeof(Int32) + sizeof(S)
  • 但是如果sizeof(S) = 2 * sizeof(Int32) + sizeof(S),我们有无限的递归,我们不能为结构分配内存;因此,递归定义是非法的。

现在,假设sizeof(Nullable<T>) = sizeof(bool) + sizeof(T)(参见Jon Skeet的回答)。考虑具有此定义的结构S

struct S
{
    int _someField;
    S? _someOtherField;
}

在这种情况下,sizeof(S) = sizeof(Int32) + sizeof(Nullable<S>)

sizeof(Nullable<S>)替换sizeof(bool) + sizeof(S),我们得到

sizeof(S) = sizeof(Int32) + sizeof(bool) + sizeof(S)

再次,无限递归。

答案 4 :(得分:1)

结构是值类型。因此,嵌套结构创建一个内存结构,该结构采用无限量的ram。类是引用类型。因此,嵌套类创建的内存结构可能是无限的,但在初始化时,它仍然很小。

答案 5 :(得分:0)

编译器正在遍历结构以确定是否存在循环。

虽然结构中包含的引用类型将放在堆中,并且编译器在查找循环时会以不同方式对它们进行处理,但可空类型的实际类型是Nullable是结构。因此编译器会看到一个结构Nullable并将其视为循环引用。

有一种解决方法 - 从界面继承:

public interface IFoo
{
}

public struct Foo : IFoo
{
    IFoo Foo;
}

因为接口是间接层,所以编译器会将您的struct视为引用类型。