【问题标题】:Why can't structs contain nullable circular references?为什么结构不能包含可为空的循环引用?
【发布时间】:2011-11-15 19:13:57
【问题描述】:

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

struct Foo
{
    Foo? bar;
}

显然这很容易导致堆栈溢出和循环引用,如果不小心的话,但bar 不应该是指向另一个Foo 实例的指针,并且默认为null?或者(更有可能)我不了解可空值类型在内存中的布局方式?

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

【问题讨论】:

    标签: c# .net struct circular-reference


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

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

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

      因为这是一个 value 类型,所以您的 Foo 最终会看起来像这样:

      struct Foo
      {
          Foo barValue;
          bool hasBarValue;
      }
      

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

      【讨论】:

      • 正如我所怀疑的 - 我的问题是不知道 Nullable 是如何在内存中布局的。这现在更有意义了!
      • @RoyiNamir:是的 - 想想它看起来像 Foo 是一个值类型......
      【解决方案3】:

      Foo? bar 是

      的快捷方式
      Nullable<Foo> bar;
      

      Nullable&lt;T&gt; 是一个大致如下所示的结构:

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

      在Foo 的情况下,Nullable&lt;Foo&gt; 将持有Foo,而Foo 又持有Nullable&lt;Foo&gt;,而Nullable&lt;Foo&gt; 反过来...

      【讨论】:

        【解决方案4】:

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

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

        现在,假设 sizeof(Nullable&lt;T&gt;) = sizeof(bool) + sizeof(T)(请参阅 Jon Skeet 的回答)。考虑具有以下定义的结构 S:

        struct S
        {
            int _someField;
            S? _someOtherField;
        }
        

        在这种情况下,sizeof(S) = sizeof(Int32) + sizeof(Nullable&lt;S&gt;)。

        用sizeof(bool) + sizeof(S)替换sizeof(Nullable&lt;S&gt;),我们得到

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

        再次,无限递归。

        【讨论】:

          【解决方案5】:

          结构是值类型。因此,嵌套结构创建了一个内存结构,它需要 infinite 数量的 ram。类是引用类型。所以,嵌套类创建的内存结构可以是无限的,但在初始化时,它仍然很小。

          【讨论】:

          • 我明白这一点 - 我错过了 Nullable 是一个通用结构,而不是指向 Foo 的指针。
          【解决方案6】:

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

          虽然结构中包含的引用类型将被放入堆中并且编译器在查找循环时会以不同的方式对待它们,但可空类型的真正类型是 Nullable 是一个结构。所以编译器看到一个结构 Nullable 并认为它是一个循环引用。

          有一种方法可以解决这个问题 - 从接口继承:

          public interface IFoo
          {
          }
          
          public struct Foo : IFoo
          {
              IFoo Foo;
          }
          

          因为接口是一个间接级别,编译器会将你的结构视为引用类型。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-09-26
            • 1970-01-01
            • 2019-10-17
            • 1970-01-01
            • 1970-01-01
            • 2021-02-27
            相关资源
            最近更新 更多