Цикл в структуре структуры, которая не существует - PullRequest
18 голосов
/ 15 февраля 2012

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

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 #?

Ответы [ 5 ]

28 голосов
/ 15 февраля 2012

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

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

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

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

11 голосов
/ 15 февраля 2012

Причина, по которой это создает цикл, заключается в том, что 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;
}
4 голосов
/ 15 февраля 2012

Реальная проблема в этой строке:

public info? c;

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

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

2 голосов
/ 15 февраля 2012

Нет никакого способа добиться изменчивой семантики значений для элементов переменного размера (семантически, я думаю, что вам нужно, чтобы 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 - Открытый экземпляр - Возвращает новый объект MutableInfo с данными, скопированными из оригинала, вызывая AsNewFullyMutable для любых вложенных ссылок.

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

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

  4. AsImmutable - Публичный виртуальный - для ImmutableInfo верните себя; для MutableInfo звоните AsNewImmutable на себя.

  5. AsMutable - общедоступный виртуальный - для MutableInfo верните себя; для ImmutableInfo звоните AsNewMutable на себя.

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

1 голос
/ 15 июля 2019

Отказ от ответственности : Это может не достичь цели "структурировать поведение типа значения".

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

public struct info
{
    public float a, b;
    public info? c
    {
        get
        {
            return cArray[nextIndex];
        }
        set
        {
            steps[nextIndex] = value;
        }
    }
    private info?[] cArray;

    public info(float a, float b, info? c = null)
    {
        this.a = a;
        this.b = b;
        this.cArray = new info?[] { c }
        this.c = c;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...