Цикл в структуре макета, который не существует
Это упрощенная версия моего кода:
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" классом вместо структуры и дать ему функцию-член для возврата копии, которую я использовал при передаче. Фактически имитируя то же поведение, что и структура, но с классом.
Я также создал следующий вопрос, ища ответ.
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
.
Нет никакого способа достичь изменяемой семантики значений элементов переменного размера (семантически, я думаю, что вы хотите, чтобы
Было бы неплохо, если бы будущая версия .net могла иметь некоторое понятие "ссылки на значения", так что копирование структуры не будет просто копировать все ее поля. Есть некоторая ценность в том, что .net не поддерживает все тонкости конструкторов копирования C++, но также было бы полезно разрешить хранилищам типа 'struct' иметь идентификатор, который будет быть связанным с местом хранения, а не с его содержимым. Учитывая, что .net в настоящее время не поддерживает ни одной такой концепции, однако, если вы хотите, чтобыMyInfo1 = MyInfo2
генерировал новый связанный список, который отделен от того, который был запущен MyInfo2). Можно заменитьinfo?
наinfo[]
(который всегда будет либо нулевым, либо заполненным одноэлементным массивом), или классом держателя, который обертывает экземплярinfo
, но семантика, вероятно, не будет тем, что вы ищете. ПослеMyInfo1 = MyInfo2
, изменения вMyInfo1.a
не повлияет наMyInfo2.a
, и изменения вMyInfo1.c
не повлияют наMyInfo2.c
, но изменения вMyInfo1.c[0].a
повлияют наMyInfo2.c[0].a
.info
был изменяемым, вам придется либо мириться с изменяемой ссылочной семантикой (включая защитное клонирование), либо со странной и дурацкой семантикой гибридных структур. Одно предложение, которое я бы сделал, если бы речь шла о производительности, состояло бы в том, чтобы иметь абстрактный классInfoBase
с потомкамиMutableInfo
иImmutableInfo
, а также со следующими члены:
AsNewFullyMutable
-- Public instance -- возвращает новый объектMutableInfo
с данными, скопированными из оригинала, вызываяAsNewFullyMutable
по любым вложенным ссылкам.
AsNewMutable
-- Public instance -- возвращает новый объектMutableInfo
с данными, скопированными из оригинала, вызываяAsImmutable
по любым вложенным ссылкам.
AsNewImmutable
-- защищенный экземпляр -- возвращает новый объектImmutableInfo
с данными, скопированными из исходного объекта, вызываяAsImmutable
(неAsNewImmutable
) на любом вложенном объекте. Рекомендации.
AsImmutable
-- Public virtual -- дляImmutableInfo
, верните себя; дляMutableInfo
, вызовитеAsNewImmutable
на себя.
AsMutable
-- Public virtual -- дляMutableInfo
, верните себя; дляImmutableInfo
, вызовитеAsNewMutable
на себя.При клонировании объекта, в зависимости от того, ожидалось ли, что объект или его потомки будут клонированы снова, прежде чем он должен был мутировать, можно было бы вызвать либо
AsImmutable
,AsNewFullyMutable
, илиAsNewMutable
. В сценариях, где один ожидая, что объект будет многократно клонирован в целях защиты, объект будет заменен неизменяемым экземпляром, который больше не будет клонироваться до тех пор, пока не возникнет желание его видоизменить.