Почему структуры не поддерживают наследование?

Я знаю, что структуры .NET не поддерживают наследование, но не совсем понятно, почему они ограничены таким образом.

Какая техническая причина предотвращает наследование структур от других структур?

10 Solutions collect form web for “Почему структуры не поддерживают наследование?”

Типы значений причины не могут поддерживать наследование из-за массивов.

Проблема заключается в том, что для производительности и соображений GC массивы типов значений хранятся «inline». Например, с учетом new FooType[10] {...} , если FooType является ссылочным типом, в управляемой куче будет создано 11 объектов (один для массива и 10 для каждого экземпляра типа). Если FooType – это тип значения, в управляемой куче будет создан только один экземпляр – для самого массива (поскольку каждое значение массива будет сохранено «inline» с массивом).

Теперь предположим, что у нас есть наследование типов значений. В сочетании с описанным выше «встроенным хранилищем» поведение массивов происходит, как видно на C ++ , «Плохие вещи».

Рассмотрим этот псевдо-C # код:

 struct Base { public int A; } struct Derived : Base { public int B; } void Square(Base[] values) { for (int i = 0; i < values.Length; ++i) values [i].A *= 2; } Derived[] v = new Derived[2]; Square (v); 

По нормальным правилам преобразования Derived[] конвертируется в Base[] (лучше или хуже), поэтому, если вы используете s / struct / class / g для приведенного выше примера, он будет компилироваться и запускаться, как ожидалось, без проблем , Но если Base и Derived являются типами значений, а массивы хранят значения inline, тогда у нас есть проблема.

У нас проблема, потому что Square() ничего не знает о Derived , она будет использовать только арифметику указателя для доступа к каждому элементу массива, увеличиваясь на постоянную величину ( sizeof(A) ). Сборка будет смутно напоминать:

 for (int i = 0; i < values.Length; ++i) { A* value = (A*) (((char*) values) + i * sizeof(A)); value->A *= 2; } 

(Да, это отвратительная assembly, но дело в том, что мы будем наращивать массив по известным константам времени компиляции, не зная, что используется производный тип.)

Итак, если это действительно произошло, у нас будут проблемы с повреждением памяти. В частности, в Square() values[1].A*=2 действительно будут изменять values[0].B !

Попробуйте отладить THAT !

Представьте, что наследуемые структуры поддерживаются. Затем объявив:

 BaseStruct a; InheritedStruct b; //inherits from BaseStruct, added fields, etc. a = b; //?? expand size during assignment? 

будет означать, что переменные структуры не имеют фиксированного размера, и поэтому у нас есть ссылочные типы.

Еще лучше, подумайте:

 BaseStruct[] baseArray = new BaseStruct[1000]; baseArray[500] = new InheritedStruct(); //?? morph/resize the array? 

Структуры не используют ссылки (если они не помещены в бокс, но вы должны стараться избегать этого), таким образом, polymorphism не имеет смысла, поскольку нет указателя с помощью ссылочного указателя. Объекты обычно живут в куче и ссылаются через указатели ссылок, но структуры выделяются в стеке (если они не помещены в бокс) или не выделяются «внутри» памяти, занятой ссылочным типом в куче.

Вот что говорят документы :

Структуры особенно полезны для небольших структур данных, имеющих семантику значений. Сложные числа, точки в системе координат или пары ключ-значение в словаре – все это хорошие примеры структур. Ключом к этим структурам данных является то, что у них мало членов данных, что они не требуют использования наследования или ссылочной идентификации и что их можно удобно реализовать с использованием семантики значений, где присваивание копирует значение вместо ссылки.

В принципе, они должны хранить простые данные и, следовательно, не имеют «дополнительных функций», таких как наследование. Возможно, было бы технически возможно, чтобы они поддерживали ограниченный вид наследования (а не polymorphism из-за того, что они были в стеке), но я считаю, что это также выбор дизайна, который не поддерживает наследование (как и многие другие вещи в .NET. языки.)

С другой стороны, я согласен с преимуществами наследования, и я думаю, что все мы достигли точки, когда мы хотим, чтобы наша struct наследовала от другой, и понимаем, что это невозможно. Но в этот момент структура данных, вероятно, настолько продвинута, что в любом случае она должна быть classом.

Классовое наследование невозможно, так как структура укладывается непосредственно в стек. Наследующая структура будет больше, чем родительская, но JIT этого не знает, и старается слишком много использовать слишком мало места. Звучит немного неясным, давайте напишем пример:

 struct A { int property; } // sizeof A == sizeof int struct B : A { int childproperty; } // sizeof B == sizeof int * 2 

Если бы это было возможно, оно бы разбилось на следующий fragment:

 void DoSomething(A arg){}; ... B b; DoSomething(b); 

Пространство выделяется для sizeof A, а не для sizeof B.

Я хочу исправить ситуацию. Несмотря на то, что структура причин не может быть унаследована, потому что они живут в стеке, является правильным, это в точности одно правильное объяснение. Структуры, как и любой другой тип значения, могут находиться в стеке. Поскольку это будет зависеть от того, где объявлена ​​переменная, они будут либо жить в стеке, либо в куче . Это будет когда они являются локальными переменными или полями экземпляра соответственно.

Говоря это, Сесил имеет имя, пригвоздив его правильно.

Я хотел бы подчеркнуть это, типы значений могут жить в стеке. Это не значит, что они всегда это делают. Локальные переменные, включая параметры метода, будут. Все остальные не будут. Тем не менее, по-прежнему остается причиной того, что они не могут быть унаследованы. 🙂

Структуры выделяются в стеке. Это означает, что семантика значения в значительной степени бесплатна, и доступ к членам структуры очень дешев. Это не предотвращает polymorphism.

Вы можете создать каждую структуру с указателем на свою таблицу виртуальных функций. Это будет проблемой производительности (каждая структура будет по меньшей мере размером указателя), но это выполнимо. Это позволит использовать виртуальные функции.

Как насчет добавления полей?

Ну, когда вы выделяете структуру в стеке, вы выделяете определенное количество места. Требуемое пространство определяется во время компиляции (раньше или когда JITting). Если вы добавляете поля и затем назначаете базовый тип:

 struct A { public int Integer1; } struct B : A { public int Integer2; } A a = new B(); 

Это перезапишет какую-то неизвестную часть стека.

Альтернативой для среды выполнения является предотrotation этого, только записывая байты sizeof (A) в любую переменную A.

Что произойдет, если B переопределяет метод в A и ссылается на его поле Integer2? Либо среда выполнения генерирует исключение MemberAccessException, либо метод вместо этого обращается к некоторым случайным данным в стеке. Ни одно из них не допустимо.

Совершенно безопасно иметь наследование структуры, если вы не используете structs полиморфно или до тех пор, пока вы не добавляете поля при наследовании. Но это не очень полезно.

Это кажется очень частым вопросом. Мне хочется добавить, что типы значений сохраняются «на месте», где вы объявляете переменную; кроме деталей реализации, это означает, что нет заголовка объекта, который говорит что-то об объекте, только переменная знает, какие данные там находятся.

Структуры поддерживают интерфейсы, поэтому вы можете сделать некоторые полиморфные вещи таким образом.

IL – это язык на основе стека, поэтому вызов метода с аргументом выглядит примерно так:

  1. Вставьте аргумент в стек
  2. Вызовите метод.

Когда метод запускается, он выталкивает некоторые байты из стека, чтобы получить его аргумент. Он точно знает , сколько байтов выскочит, потому что аргумент является либо указателем ссылочного типа (всегда 4 байта на 32-битном), либо является типом значения, для которого размер всегда известен точно.

Если это указатель ссылочного типа, то метод ищет объект в куче и получает его дескриптор типа, который указывает на таблицу методов, которая обрабатывает этот конкретный метод для этого точного типа. Если это тип значения, то поиск в таблице методов не требуется, потому что типы значений не поддерживают наследование, поэтому существует только одна возможная комбинация метода / типа.

Если типы значений поддерживали наследование, тогда были бы дополнительные накладные расходы в том, что конкретный тип структуры должен был бы помещаться в стек, а также его значение, что означало бы какой-то поиск таблицы методов для конкретного конкретного экземпляра типа. Это позволит исключить преимущества скорости и эффективности типов значений.

  • Как расширить class без использования супер в ES6?
  • Как определить class объекта?
  • Как я могу инициализировать переменные-члены базового classа в конструкторе производного classа?
  • Подclass / наследование стандартных контейнеров?
  • Разница между частным, общественным и защищенным наследованием
  • Какова точка частной чистой виртуальной функции?
  • Java: если A расширяет B и B расширяет Object, то это множественное наследование
  • Нельзя наследовать от std :: vector
  • можно ли вызвать переопределенный метод из родительской структуры в golang?
  • Почему я не могу наследовать статические classы?
  • Запуск JavaFX из основного метода classа, который не расширяет Application
  • Interesting Posts

    Обратимый генератор псевдослучайных последовательностей

    Не удается переустановить Mac os x 10.10

    Как скопировать файл один раз и вставить в несколько подпапок?

    Захват stdout из команды system () оптимально

    Мой компьютер не является телефоном – как удалить экран предварительного просмотра Swipey из Gnome 3?

    Как переименовать набор файлов в командной строке Windows?

    Как увеличить размер шрифта терминала на OSX?

    Командная строка VS2010 дает ошибку: не удается определить местоположение папки VS Common Tools

    Возможно ли, чтобы виртуальная виртуальная машина всегда начиналась с того же времени, если да, то как?

    Как получить версию Windows в качестве переменной?

    Как написать вывод консоли в txt-файл

    Как я могу отключить сетевое устройство на Mac?

    Аутентификация на основе токенов в ядре ASP.NET (обновлена)

    Элемент ViewData с ключом «XXX» имеет тип «System.Int32», но должен иметь тип «IEnumerable »,

    Целостность системных файлов Windows 7

    Давайте будем гением компьютера.