我理解为什么结构不能包含导致逻辑内存问题的循环引用,但为什么可空引用不能绕过这个限制呢?例如:
struct Foo
{
Foo? bar;
}
显然,这很容易导致堆栈溢出和循环引用,如果不小心,但不应该bar
是指向另一个Foo
实例的指针,并且默认为{{1 }}?或者(更有可能)我不明白在内存中如何排列可以为空的值类型?
(我的背景知识主要包括来自this question and answers的信息。)
答案 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视为引用类型。