Подтвердить что ты не робот

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

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

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" классом вместо структуры и предоставить ему функцию-член, чтобы вернуть копию, которую я использовал при ее передаче. По сути, имитирует то же поведение, что и структура, но с классом.

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

Ответ 3

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

public info? c;

Так как это struct, С# должен знать внутренний макет info/s, прежде чем он сможет создать внешний макет info. Внутренний info включает внутренний внутренний info, который, в свою очередь, включает внутреннюю внутреннюю внутреннюю info и т.д. Компилятор не может создать макет из-за этой проблемы с круговой ссылкой.

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

Ответ 4

Существует не какой-либо способ получить изменчивую семантику значений элементов с переменным размером (семантически, я думаю, что вы должны иметь 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 не поддерживает все тонкости конструкторов копирования С++, но также было бы полезно, если бы хранилища хранилища типа 'struct' имели идентификатор, который был бы связан с местом хранения, а не его содержание.

Учитывая, что .net в настоящее время не поддерживает какое-либо такое понятие, однако, если вы хотите, чтобы info был изменчивым, вам придется либо мириться с изменяемой ссылочной семантикой (включая защитное клонирование), либо со странным и wacky struct-class-hybrid semantics. Одно из предложений, которое я хотел бы иметь, если производительность представляет собой проблему, состоит в том, чтобы иметь абстрактный класс InfoBase с потомками MutableInfo и ImmutableInfo и со следующими членами:

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

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

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

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

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

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

Ответ 5

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

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

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