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

Почему типы значений .NET закрыты?

Невозможно наследовать от С# struct. Для меня это не очевидно, почему это:

  • Очевидно, что у вас не может быть ссылочного типа, который наследуется от типа значения; это не сработает
  • Неразумно наследовать от одного примитивного типа (Int32, Double, Char и т.д.).
  • Вам нужно будет иметь возможность вызывать (не виртуальные) методы на базе с использованием производного экземпляра. Вы можете отличить от производной структуры до базы, поскольку они будут перекрывать одну и ту же память. Я думаю, что отбрасывание с базы на производную не сработало бы, так как вы не могли знать тип производной структуры во время выполнения.
  • Я вижу, что вы не могли реализовать виртуальные методы в своей иерархии классов, поскольку типы значений не могут иметь виртуальных членов

Интересно, является ли это техническим ограничением в CLR или чем-то, что компилятор С# не позволяет вам делать?

Изменить: Типы значений не могут иметь виртуальные методы, и я понимаю, что это ограничение исключает большинство сценариев, в которых вы хотели бы использовать наследование. Тем не менее, это все еще оставляет наследование-как-агрегацию. Представьте себе структуру Shape с полем Colour: я могу написать код, который принимает любую структуру, полученную из Shape, и получить доступ к своему полю Colour, даже если я никогда не могу написать виртуальный метод Shape.Draw.

Я могу вспомнить один сценарий, который будет разбит непечатаемыми типами значений. Предполагается, что типы значений реализуют Equals и GetHashCode правильно; даже несмотря на то, что эти два метода на System.Object являются виртуальными, их называют не виртуальными по типам значений. Даже если типы значений не были запечатаны, кто-то, написавший структуру, полученную из другого, не мог написать собственную реализацию этих двух методов и ожидать, что их правильно назовут.

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

Изменить 2: Я просто заметил этот очень похожий вопрос, ответ на который эффективно ", потому что тогда массивы значений типов wouldn ' t work".

4b9b3361

Ответ 1

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

Типы значений формируют исключение из правила .NET, точно для того, чтобы облегченные объекты не требовали косвенности через ссылки. Поэтому полиморфизм во время выполнения не работает для них, и большинство аспектов наследования становятся бессмысленными.

(Theres исключение: объект типа значения может быть помещен в бокс, что позволяет вызывать виртуальные методы, унаследованные от System.Object.)

Чтобы обратиться к одному из ваших пунктов:

  • Вы можете отличить от производной структуры до базы, поскольку они будут перекрывать одну и ту же память.

Нет, это было бы невозможно - выбор типа значения скопировал бы его значение. Здесь не были ссылки на ссылки, поэтому в памяти не было перекрытия. Поэтому отличать тип значения к его базовому типу бессмысленно (опять же, если не говорить о преобразовании в object, который фактически выполняет бокс под капотом, а также работает с копией значения).

Все еще не ясно? Давайте посмотрим на пример.

Допустим, что мы получили гипотетический struct Shape и, наследуя от него, struct Circle. Shape определяет виртуальный Draw метод (который принимает объект Graphics). Теперь, скажем, мы хотим нарисовать фигуру на холсте. Это, конечно, отлично работает:

var circle = new Circle(new Point(10, 10), 20);
circle.Draw(e.Graphics); // e.Graphics = graphics object of our form.

- Но здесь мы вообще не используем наследование. Чтобы использовать наследование, представьте вместо этого следующий вспомогательный метод DrawObject:

void DrawObject(Shape shape, Graphics g) {
    // Do some preparation on g.
    shape.Draw(g);
}

И мы называем это в другом месте с помощью Circle:

var circle = new Circle(new Point(10, 10), 20);
DrawObject(circle, e.Graphics);

- И, ka-blam - этот код не рисует круг. Зачем? Потому что, когда мы передаем круг методу DrawObject, мы делаем две вещи:

  • Скопируем его.
  • Мы срезаем его, т.е. объект Shape действительно больше не является Circle - ни оригинальным, ни копией. Вместо этого его часть Circle была "нарезана" во время копирования, и остается только часть Shape. shape.Draw теперь вызывает метод Draw Shape, а не Circle.

В С++ вы можете фактически вызвать это поведение. По этой причине ООП на С++ работает только по указателям и ссылкам, а не по типам значений напрямую. И по той же причине .NET разрешает наследование ссылочных типов, потому что в любом случае вы не можете использовать его для типов значений.

Обратите внимание, что приведенный выше код работает в .NET, если Shape является интерфейсом. Другими словами, ссылочный тип. Теперь ситуация другая: ваш объект Circle все равно будет скопирован, но он также будет помещен в ссылку.

Теперь .NET теоретически позволит вам наследовать struct от class. Тогда приведенный выше код будет работать так же хорошо, как если бы Shape был интерфейсом. Но тогда все преимущество использования struct в первую очередь исчезает: для всех целей и целей (кроме локальных переменных, которые никогда не передаются другому методу, следовательно, нет полезности наследования), ваш struct будет вести себя как неизменяемый ссылочный тип вместо типа значения.

Ответ 2

Из ECMA 335: Типы значений будут запечатаны, чтобы избежать проблем с усложнением среза. Более жесткие правила, указанные здесь, обеспечивают более эффективную реализацию без особого компрометирующая функциональность.

Я не знаю, что означает "срез значений", но я бы предположил, что они запечатаны, чтобы обеспечить эффективную реализацию CLR.

Ответ 3

У вас может быть ограниченная форма наследования с использованием параметров типового типа для типов значений.

Ответ 4

Поскольку каждый экземпляр типа hase различается по размеру и сохраняется в стеке. Итак, если вы пишете "Base = Derived", где "Base" и "Derived" являются типами значений, вы будете повреждать стек.