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

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

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 ответа

Решение

Недопустимо иметь структуру, которая содержит себя в качестве члена. Это связано с тем, что структура имеет фиксированный размер, и она должна быть не меньше суммы размеров каждого из ее членов. Ваш тип должен иметь 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;

Так как это structC# нужно знать внутреннее 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 чтобы быть изменчивым, вам придется либо мириться с изменяемой ссылочной семантикой (включая защитное клонирование), либо со странной и причудливой семантикой struct-class-hybrid. Одно из предложений, которое у меня возникло бы, если бы производительность была проблемой, было бы иметь абстрактный InfoBase класс с потомками MutableInfo а также ImmutableInfoи со следующими членами:

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

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

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

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

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

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

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

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

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;
    }
}
Другие вопросы по тегам