Почему структуры не могут содержать пустые круглые ссылки? - PullRequest
3 голосов
/ 15 ноября 2011

Я понимаю, почему структуры не могут содержать циклические ссылки, которые приводят к проблемам логической памяти, но почему обнуляемая ссылка не может обойти это ограничение?Например:

struct Foo
{
    Foo? bar;
}

Очевидно, что это может очень легко привести к переполнению стека и циклическим ссылкам, если не соблюдать осторожность, но не должно bar быть указателем на другой экземпляр Foo, ипо умолчанию null?Или (более вероятно) я не понимаю, как типы значений, допускающие значение nullable, располагаются в памяти?

(Мои базовые знания в основном состоят из информации этого вопроса и ответов .)

Ответы [ 6 ]

8 голосов
/ 15 ноября 2011

Нет, не совсем.Обнуляемый тип значения действительно является экземпляром Nullable<> с типом значения в качестве универсального параметра.Знак вопроса - это просто сокращение.

Nullable является структурой и, следовательно, является типом значения.Поскольку он сохраняет ссылку на структуру Foo, у вас все еще есть циклическая ссылка, состоящая из типов значений.

6 голосов
/ 15 ноября 2011

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

Теперь, надеюсь, более очевидно, что это проблема:)

5 голосов
/ 15 ноября 2011

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>, который в свою очередь ...

2 голосов
/ 15 ноября 2011

Как вы, вероятно, понимаете, структура не может иметь циклическую ссылку, потому что, когда вы размещаете структуру в памяти, вы должны включить хранение внутри структуры для каждого из ее членов. Циклическое определение требует бесконечного количества памяти:

  • Структура с двумя 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) (см. Ответ Джона Скита). Рассмотрим структуру 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)

Опять бесконечная рекурсия.

1 голос
/ 15 ноября 2011

структуры являются типами значений. Таким образом, вложенная структура создает структуру памяти, которая занимает бесконечное количество оперативной памяти. Классы являются ссылочными типами. Итак, вложенный класс создает структуру памяти, которая может быть бесконечной, но при инициализации она все еще мала.

0 голосов
/ 15 ноября 2011

Компилятор обходит структуру, чтобы определить, существует ли цикл.

В то время как ссылочные типы, содержащиеся в структурах, будут помещаться в кучу, а компилятор будет обрабатывать их по-разному при поиске циклов, реальный тип обнуляемого типа - Nullable - это структура.Таким образом, компилятор видит структуру Nullable и считает ее циклической ссылкой.

Существует способ обойти это - наследовать от интерфейса:

public interface IFoo
{
}

public struct Foo : IFoo
{
    IFoo Foo;
}

Поскольку интерфейсы являются уровнем косвенностикомпилятор будет обрабатывать вашу структуру как ссылочный тип.

...