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

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

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

edit:

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

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

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

4 ответов


незаконно иметь структуру, которая содержит себя в качестве члена. Это потому, что структура имеет фиксированный размер, и он должен быть по крайней мере таким же большим, как сумма размеров каждого из его членов. Ваш тип должен иметь 8 байт для двух поплавков, по крайней мере один байт, чтобы показать, является лиinfo равно null, плюс размер другого 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's макет. И внутреннее 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 -- защищенный instance -- возвращает новый ImmutableInfo объект, с данными, скопированными из оригинала, вызывая AsImmutable (не AsNewImmutable) по любым вложенным ссылкам.

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

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

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