Цикл в структуре макета, который не существует


Это упрощенная версия моего кода:

public struct info
{
    public float a, b;
    public info? c;

    public info(float a, float b, info? c = null)
    {
        this.a = a;
        this.b = b;
        this.c = c;
    }
}

Проблема заключается в ошибке Struct member 'info' causes a cycle in the struct layout. я ищу структуру, подобную поведению типа значения. Я мог бы смоделировать это, используя класс и функцию-клон, но я не вижу, зачем мне это нужно.

Как эта ошибка истинна? Рекурсия, возможно, могла бы вызвать построение навсегда в некоторых подобных ситуациях, но я не могу придумать никакого способа, которым она могла бы в этом случае. Ниже приведены примеры, которые должны быть в порядке, если программа будет компилировать.

new info(1, 2);
new info(1, 2, null);
new info(1, 2, new info(3, 4));

Правка:

Решение, которое я использовал, состояло в том, чтобы сделать "info" классом вместо структуры и дать ему функцию-член для возврата копии, которую я использовал при передаче. Фактически имитируя то же поведение, что и структура, но с классом.

Я также создал следующий вопрос, ища ответ.

Определение класса типа значения в C#?

4 17

4 ответа:

Это не законно иметь структуру, которая содержит себя в качестве члена. Это происходит потому, что структура имеет фиксированный размер , и она должна быть по крайней мере такой же большой, как сумма размеров каждого из ее членов. Ваш тип должен иметь 8 байт для двух поплавков, по крайней мере один байт, чтобы показать, является ли info нулевым, плюс размер другого info. Это дает следующее неравенство:

 size of info >= 4 + 4 + 1 + size of info

Очевидно, что это невозможно, поскольку для этого требуется, чтобы ваш тип был бесконечно большой.

Вы должны использовать ссылочный тип (т. е. класс). Вы можете сделать свой класс неизменяемым и переопределить Equals и GetHashCode, чтобы дать поведение, подобное значению, подобное классу String.

Причина, по которой это создает цикл, заключается в том, что Nullable<T> сам является struct. Поскольку он ссылается на info, у вас есть цикл в макете (info имеет поле Nullable<info> и имеет поле info). Это по существу эквивалентно следующему

public struct MyNullable<T> {
  public T value;
  public bool hasValue;
}

struct info { 
  public float a, b;
  public MyNullable<info> next;
}

Реальная проблема находится на этой линии:

public info? c;
Поскольку это struct, C# должен знать внутреннюю info/s компоновку, прежде чем он сможет создать внешнюю info компоновку. А внутреннее info включает в себя внутреннее внутреннее info, которое в свою очередь включает в себя внутреннее внутреннее внутреннее info и так далее. Компилятор не может создать макет из-за этой проблемы циклической ссылки.

Примечание: info? c - это сокращение для Nullable<info>, которое само по себе является struct.

Нет никакого способа достичь изменяемой семантики значений элементов переменного размера (семантически, я думаю, что вы хотите, чтобы MyInfo1 = MyInfo2 генерировал новый связанный список, который отделен от того, который был запущен MyInfo2). Можно заменить info? на info[] (который всегда будет либо нулевым, либо заполненным одноэлементным массивом), или классом держателя, который обертывает экземпляр info, но семантика, вероятно, не будет тем, что вы ищете. После MyInfo1 = MyInfo2, изменения в MyInfo1.a не повлияет на MyInfo2.a, и изменения в MyInfo1.c не повлияют на MyInfo2.c, но изменения в MyInfo1.c[0].a повлияют на MyInfo2.c[0].a.

Было бы неплохо, если бы будущая версия .net могла иметь некоторое понятие "ссылки на значения", так что копирование структуры не будет просто копировать все ее поля. Есть некоторая ценность в том, что .net не поддерживает все тонкости конструкторов копирования C++, но также было бы полезно разрешить хранилищам типа 'struct' иметь идентификатор, который будет быть связанным с местом хранения, а не с его содержимым. Учитывая, что .net в настоящее время не поддерживает ни одной такой концепции, однако, если вы хотите, чтобы info был изменяемым, вам придется либо мириться с изменяемой ссылочной семантикой (включая защитное клонирование), либо со странной и дурацкой семантикой гибридных структур. Одно предложение, которое я бы сделал, если бы речь шла о производительности, состояло бы в том, чтобы иметь абстрактный класс InfoBase с потомками MutableInfo и ImmutableInfo, а также со следующими члены:
  1. AsNewFullyMutable -- Public instance -- возвращает новый объект MutableInfo с данными, скопированными из оригинала, вызывая AsNewFullyMutable по любым вложенным ссылкам.

  2. AsNewMutable -- Public instance -- возвращает новый объект MutableInfo с данными, скопированными из оригинала, вызывая AsImmutable по любым вложенным ссылкам.

  3. AsNewImmutable -- защищенный экземпляр -- возвращает новый объект ImmutableInfo с данными, скопированными из исходного объекта, вызывая AsImmutable (не AsNewImmutable) на любом вложенном объекте. Рекомендации.

  4. AsImmutable -- Public virtual -- для ImmutableInfo, верните себя; для MutableInfo, вызовите AsNewImmutable на себя.

  5. AsMutable -- Public virtual -- для MutableInfo, верните себя; для ImmutableInfo, вызовите AsNewMutable на себя.

При клонировании объекта, в зависимости от того, ожидалось ли, что объект или его потомки будут клонированы снова, прежде чем он должен был мутировать, можно было бы вызвать либо AsImmutable, AsNewFullyMutable, или AsNewMutable. В сценариях, где один ожидая, что объект будет многократно клонирован в целях защиты, объект будет заменен неизменяемым экземпляром, который больше не будет клонироваться до тех пор, пока не возникнет желание его видоизменить.