Я знаю, что структуры в .NET не поддерживают наследование, но не совсем понятно, почему они ограничены таким образом.
Какая техническая причина препятствует наследованию структур от других структур?
.net
inheritance
struct
Джульетта
источник
источник
Ответы:
Типы значений не могут поддерживать наследование по причине массивов.
Проблема в том, что из соображений производительности и сборки мусора массивы типов значений хранятся «встроенными». Например,
new FooType[10] {...}
еслиFooType
это ссылочный тип, в управляемой куче будет создано 11 объектов (по одному для массива и 10 для каждого экземпляра типа). ЕслиFooType
вместо этого используется тип значения, в управляемой куче будет создан только один экземпляр - для самого массива (поскольку каждое значение массива будет храниться «встроено» вместе с массивом).Теперь предположим, что у нас есть наследование с типами значений. В сочетании с описанным выше поведением массивов «встроенное хранилище» происходят плохие вещи, как это видно на C ++ .
Рассмотрим этот псевдо-код C #:
По обычным правилам преобразования a
Derived[]
может быть преобразован в aBase[]
(к лучшему или худшему), поэтому, если вы используете s / struct / class / g для приведенного выше примера, он будет компилироваться и работать, как ожидалось, без проблем. Но еслиBase
иDerived
являются типами значений, а массивы хранят значения встроенными, тогда у нас есть проблема.У нас проблема, потому что
Square()
ничего не известноDerived
, он будет использовать только арифметику указателя для доступа к каждому элементу массива, увеличивая его на постоянное количество (sizeof(A)
). Сборка будет примерно такой:(Да, это отвратительная сборка, но дело в том, что мы будем увеличивать массив с известными константами времени компиляции, не зная, что используется производный тип.)
Так что, если бы это действительно произошло, у нас были бы проблемы с повреждением памяти. В частности, в пределах
Square()
,values[1].A*=2
будет на самом деле быть модифицированиеvalues[0].B
!Попробуйте отладить ТО !
источник
Представьте, что структуры поддерживают наследование. Затем заявляя:
означало бы, что структурные переменные не имеют фиксированного размера, и именно поэтому у нас есть ссылочные типы.
Еще лучше подумайте об этом:
источник
Foo
наследованияBar
не должно позволятьFoo
присваивать aBar
, но объявление структуры таким образом может дать несколько полезных эффектов: (1) Создайте член типа со специальным именем вBar
качестве первого элементаFoo
иFoo
включите имена членов, которые являются псевдонимами этих членовBar
, позволяя код, который раньшеBar
был адаптирован для использованияFoo
вместо него, без необходимости заменять все ссылки наthing.BarMember
наthing.theBar.BarMember
, и сохраняя возможность читать и записывать всеBar
поля как группу; ...Структуры не используют ссылки (если они не заключены в рамку, но вы должны попытаться избежать этого), поэтому полиморфизм не имеет смысла, поскольку нет косвенного обращения через указатель ссылки. Объекты обычно находятся в куче и на них ссылаются через ссылочные указатели, но структуры выделяются в стеке (если они не помещены в коробку) или выделяются «внутри» памяти, занятой ссылочным типом в куче.
источник
Foo
, имеющий поле типа структуры,Bar
мог рассматриватьBar
члены как свои собственные, так чтоPoint3d
класс может , например , инкапсулироватьPoint2d xy
но относятся кX
этой области или какxy.X
илиX
.Вот что говорят документы :
По сути, они должны содержать простые данные и, следовательно, не имеют «дополнительных функций», таких как наследование. Вероятно, для них было бы технически возможно поддерживать какой-то ограниченный тип наследования (не полиморфизм из-за того, что они находятся в стеке), но я считаю, что это также выбор дизайна, чтобы не поддерживать наследование (как и многие другие вещи в .NET языки есть.)
С другой стороны, я согласен с преимуществами наследования, и я думаю, что все мы достигли точки, когда хотим, чтобы нас
struct
унаследовали от другого, и понимаем, что это невозможно. Но на этом этапе структура данных, вероятно, настолько развита, что в любом случае это должен быть класс.источник
Point3D
изPoint2D
; вы не сможете использоватьPoint3D
вместо aPoint2D
, но вам не придется заново реализовыватьPoint3D
полностью с нуля.) В любом случае я так интерпретировал ...class
более ,struct
когда это необходимо.Классовое наследование невозможно, поскольку структура кладется непосредственно в стек. Наследующая структура будет больше, чем родительская, но JIT этого не знает и пытается разместить слишком много на слишком маленьком пространстве. Звучит немного непонятно, давайте напишем пример:
Если бы это было возможно, это привело бы к сбою в следующем фрагменте:
Место выделяется для размера A, а не для размера B.
источник
Есть момент, который я хотел бы исправить. Несмотря на то, что структуры не могут быть унаследованы, потому что они живут в стеке, это правильное объяснение, в то же время это наполовину правильное объяснение. Структуры, как и любой другой тип значения, могут находиться в стеке. Поскольку это будет зависеть от того, где объявлена переменная, они будут находиться либо в стеке, либо в куче . Это будет, если они являются локальными переменными или полями экземпляра соответственно.
Сказав это, Сесил получил имя правильно.
Я хотел бы подчеркнуть, что типы значений могут жить в стеке. Это не значит, что они всегда так делают. Локальные переменные, включая параметры метода, будут. Все остальные не будут. Тем не менее, это все еще причина, по которой они не могут быть переданы по наследству. :-)
источник
Структуры размещаются в стеке. Это означает, что семантика значений в значительной степени бесплатна, а доступ к членам структуры очень дешев. Это не предотвращает полиморфизм.
Каждая структура может начинаться с указателя на ее таблицу виртуальных функций. Это было бы проблемой производительности (каждая структура была бы размером как минимум с указатель), но это выполнимо. Это позволит использовать виртуальные функции.
А что насчет добавления полей?
Что ж, когда вы выделяете структуру в стеке, вы выделяете определенное количество места. Требуемое пространство определяется во время компиляции (заранее или при JIT). Если вы добавляете поля, а затем присваиваете базовый тип:
Это перезапишет неизвестную часть стека.
В качестве альтернативы среда выполнения может предотвратить это, записав только байтов sizeof (A) в любую переменную A.
Что произойдет, если B переопределит метод в A и ссылается на его поле Integer2? Либо среда выполнения выдает исключение MemberAccessException, либо метод вместо этого обращается к некоторым случайным данным в стеке. Ни то, ни другое недопустимо.
Совершенно безопасно иметь наследование структур, если вы не используете структуры полиморфно или пока вы не добавляете поля при наследовании. Но это не очень полезно.
источник
Это кажется очень частым вопросом. Мне хочется добавить, что типы значений хранятся «на месте», где вы объявляете переменную; Помимо деталей реализации, это означает, что нет заголовка объекта, который что-то говорит об объекте, только переменная знает, какие данные там находятся.
источник
Структуры поддерживают интерфейсы, поэтому таким образом можно делать некоторые полиморфные вещи.
источник
IL - это стековый язык, поэтому вызов метода с аргументом происходит примерно так:
Когда метод запускается, он извлекает из стека несколько байтов, чтобы получить свой аргумент. Он точно знает , сколько байтов нужно вывести, потому что аргумент либо является указателем ссылочного типа (всегда 4 байта для 32-битного), либо это тип значения, размер которого всегда точно известен.
Если это указатель ссылочного типа, тогда метод ищет объект в куче и получает дескриптор его типа, который указывает на таблицу методов, которая обрабатывает этот конкретный метод для этого точного типа. Если это тип значения, то поиск в таблице методов не требуется, поскольку типы значений не поддерживают наследование, поэтому существует только одна возможная комбинация метода / типа.
Если типы значений поддерживают наследование, тогда возникнут дополнительные накладные расходы, так как конкретный тип структуры должен будет помещаться в стек вместе с ее значением, что будет означать своего рода поиск в таблице методов для конкретного конкретного экземпляра типа. Это устранило бы преимущества типов значений в скорости и эффективности.
источник