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

17

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

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

    
задан alan2here 15.02.2012 в 16:33
источник
  • Я предполагаю, что вам нужно иметь хотя бы один конструктор, который не принимает информацию в качестве параметра? Вы используете аргумент по умолчанию, но, возможно, C # это не нравится. Что произойдет, если вы создадите два конструктора? –  Ryan P 15.02.2012 в 16:37
  • Просто сделайте это классом; это не данные структуры –  Marc Gravell♦ 15.02.2012 в 16:50
  • info? не является указателем на информацию, это копия. Если вам это действительно нужно (не следует), почему бы не создать свой собственный тип NULL, который является классом? Вы даже можете иметь неявные операторы для преобразования в YourNullable <T> из Nullable <T>. Конечно, это означало бы лодку YourNullables, вполне вероятно, исключив любой бонус (если бы он был) от того, чтобы ваш класс был struct :) C # structs не являются C-структурами. –  Luaan 07.04.2015 в 18:51

4 ответа

26

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

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

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

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

    
ответ дан Mark Byers 15.02.2012 в 16:36
источник
  • +1 хороший совет по всем пунктам. Вы также можете сделать c-элемент объекта типа, заставляя его быть в коробке (конструктор все равно может получить информацию?). Немного уродливого, хотя –  MattDavey 15.02.2012 в 16:49
  • это действительно работает для меня. общественная информация? с; не дает никакой ошибки, он ведет себя как ссылка на структурный и ссылочный размер. –  M.kazem Akhgary 03.09.2017 в 23:57
10

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

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

public info? c;

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

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

    
ответ дан dasblinkenlight 15.02.2012 в 16:36
источник
2

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

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

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

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

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

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

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

    
ответ дан supercat 15.02.2012 в 18:28
источник