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

Основы полиморфизма

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

Итак, например:

class Shape
{
    public virtual void Draw()
    {
        Console.WriteLine("Drawing shape...");
    }
}

class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing circle...");
    }
}

static void Main()
{
    Shape theShape = new Circle();
    theShape.Draw();
}

Выводятся следующие данные:

Drawing circle...

Я всегда понимал, что при объявлении любого типа объекта это способ выделения памяти для этого конкретного типа объекта. Таким образом, Int32 i = 2l; будет означать, что я теперь отложил память как своего рода "заполнитель" для целого числа. Но в вышеприведенном коде я отложил память в сторону Shape, но он может проиндексировать ссылку/хранить объект типа Circle!?

4b9b3361

Ответ 1

Все переменные класса в С# (и в Java) на самом деле являются только ссылками - в отличие от так называемых примитивных типов (например, int, float) и structs; фактическое пространство для объекта Circle зарезервировано, когда вы пишете new Circle(), Shape theShape только резервирует место для ссылки!

Любая ссылочная переменная может содержать ссылку на все производные типы; фактическое разрешение которого для вызова (если оно объявлено virtual) происходит с использованием таблиц виртуальных методов во время выполнения (не через Reflection).

Чтобы объяснить, какой полиморфизм можно использовать для (цитирование wikipedia):

[It] позволяет обрабатывать значения разных типов данных с использованием единого интерфейса.

Общий интерфейс для объектов Shape в вашем случае будет Draw(). Было бы разумно иметь список фигур и вызывать метод Draw() для каждого из них для их отображения. Это означает, что для просмотра всех фигур вашей программе не нужно будет заботиться о том, какие формы сохраняются в этом списке - все надлежащие методы Draw() будут вызываться автоматически.

Каждая переменная класса, автоматически являющаяся ссылкой, является одной из больших отличий С# (и Java) от таких языков, как С++, где вы можете решить, где вы хотите, чтобы ваша переменная жила; для круга типа значения (в С++), вы должны написать:

Circle circle;

Если вы хотите указать на него, вы должны написать

Circle * circle = new Circle();

Java и С# не имеют явного знака, делающего переменную "указателем" или "ссылкой" - просто каждая переменная, которая должна содержать объект, является указателем/ссылкой!

Также обратите внимание, что (например, на С++) вы можете использовать только полиморфизм, если используете указатели или ссылки; что, поскольку типы значений могут быть доступны только как то, что они были объявлены, а не больше; со ссылками и указателями, когда ваша действительная переменная ссылается только на/указывая на что-то, она может указывать на ряд вещей (независимо от того, что компилятор позволяет ей указать).

Ответ 2

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

Компилятор не проводит такую ​​оценку; компилятор завершается задолго до запуска кода. Время выполнения оценивает, к какому типу объекта относится, чтобы решить, какой виртуальный метод нужно вызвать. Он не делает этого, используя Reflection.

Что оценивает компилятор, так это то, что использовать слот виртуального метода, когда метод вызывается во время выполнения. Компилятор выдает инструкции, в которых говорится: "Время выполнения, когда этот код работает, опросить этот объект в этом слоте и посмотреть, какой метод хранится в этом слоте, и выполнить его".

Образовательно видеть, как вы можете реализовать виртуальные методы на С#, если у С# не было их встроенных. Смотрите мою трехчастную серию статей о том, что.

Я всегда понимал, что при объявлении любого типа объекта это способ выделения памяти для этого конкретного типа объекта.

Теперь самое время в вашем образовании начать правильно использовать слова типа "объявление", "объект" и т.д. Объекты не объявлены. Объявляются типы. Переменные объявлены.

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

Это абсолютно фундаментально для С#, поэтому убедитесь, что вы это понимаете. Локальная переменная типа string не содержит строку. Он содержит ссылку на строку; строка находится где-то в другом месте, и ссылка ссылается на это местоположение.

В приведенном выше коде я отложил память в сторону Shape, но на самом деле он может ссылаться на объект типа Circle!?

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

Если у вас есть ноутбук, который содержит адреса ваших друзей, он может содержать ссылку на блок в многоквартирном доме и может содержать ссылку на дом. В записной книжке нет жилого дома или дома. Квартиры и дома - это оба вида жилья; ваш блокнот содержит ссылки на жилые помещения.

Предположим, что друг покупает землю, строит дом и отправляет вам свой новый адрес. Вам не нужно выделять место в своем ноутбуке для дома. Департамент городского зонирования уже выделил место для строительства дома в другом месте. Вы должны выделить место в своем ноутбуке для адреса жилого помещения. Тот факт, что дом является своего рода жилищем, является тем, что делает его законным, чтобы поместить адрес в ваш блокнот.

Когда вы создаете объект, являющийся экземпляром ссылочного типа, среда выполнения является зонирующим отделом - она ​​заботится о распределении хранилища для фактического объекта. Конструктор "строит дом". Локальная переменная назначается для хранения ссылки на хранилище для фактического объекта.

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

Я не уверен, что это отвечает на ваш вопрос, потому что вы, похоже, не задаете вопрос в своем вопросе. В чем ваш вопрос?

Ответ 3

class Contact {
  public string FirstName;
  public string LastName;
}

class Customer : Contact {
  public int OrderNumber;
}

enter image description here

Когда метод, который ожидает ссылки на Контакт, фактически получает ссылку на Клиента, он все равно работает, потому что ссылка Клиента также является ссылкой на Контакт.

Ответ 4

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

Отражение - это инструмент для нас, разработчики получают информацию о времени выполнения из данного экземпляра объекта или типа и не более.

Компилятор идет намного дальше, чем это.

Ответ 5

I've put memory aside for a Shape but it can infact reference/store an object of type Circle!?

Нет, вы здесь не правы. Когда вы выполняете Circle(), память, которая будет выделена здесь, будет основана на классе Circle not Shape.

Что вы здесь делаете, так это создание указателя класса Shape и использование этого указателя, указывающего на объект/память класса окружения. Поскольку через полиморфизм вы можете указать объект дочернего класса (круг) через указатель (форму) базового класса, то почему вы можете написать

Shape shape = new Circle();

Ответ 6

Когда вы объявляете Shape, вы отбрасываете только память, которая будет указывать на объект после его создания (вы создаете ссылку).

Когда вы создаете экземпляр Circle, память потребляется, и пространство, которое вы зарезервировали вместе с объявлением, теперь указывает на ваш Circle.

Что касается вызова соответствующего метода, среда выполнения вообще не использует Reflection. Вся информация хранится в таблице Таблица виртуальных методов и разрешается при выполнении вызова.

Ответ 7

Вы выделили память для круга, вызвав new в классе Circle. Вы просто сохранили его в объекте Shape. Это возможно, потому что класс Circle содержит объект Shape в качестве базы.

Когда вы выполните следующую строку:

Shape theShape = new Circle();

Вы говорите, что создаете объект Circle и используете объект Shape, чтобы указать на него. Поскольку вы указываете на объект типа Circle, вызывается функция переопределения для Draw().

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

Ответ 8

В С# или вообще в .NET существуют два типа объектов: типы значений и ссылочные типы. Классы всегда являются ссылочными типами. Таким образом, форма, которую вы назначаете, - это просто ссылка или указатель, а не полный блок памяти для формы.

Ответ 9

Я всегда понимал, что при объявлении любого типа объекта это способ сортировки памяти для этого конкретного типа объекта.

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

Выражение new будет выполнять выделение памяти для фактического объекта.

Когда вы вызываете theShape.Draw(), это среда выполнения .NET, которая определяет, какой фактический метод будет вызываться; в этом случае для Circle. (Компилятор не может принять это решение в целом.)

Ответ 10

При объявлении чего-то как

Shape theShape;

вы сообщаете компилятору, что "theShape" будет содержать объект, который является Shape, или может претендовать на то, чтобы быть одним (т.е. из-за его дочернего элемента). Таким образом, вы говорите, что вы можете вызывать любые методы, свойства и т.д. На theShape, которые существуют в объекте Shape.

Когда вы говорите:

Shape theShape = new Circle();

то вы можете сказать, что говорите выше, и дополнительно заявляете, что theShape на самом деле является этим новым объектом Circle. Очевидно, мы знаем, что Circle с радостью выполнит любой из методов, свойств и т.д. Shape, так что это нормально.

Если бы мы тогда сказали:

theShape.CircleMethod();

Тогда все пойдет не так. Хотя мы знаем, что theShape является Circle, компилятор этого не знает. Все, что мы сказали, это то, что его a Shape и который на нем не имеет метода CircleMethod, поэтому вышеупомянутый вызов недействителен.

Если вам интересно, почему это так, подумайте о следующем коде:

public void doSomething()
{
    Shape theShape = getShape();
    theShape.CircleMethod();
}

public Shape getShape()
{
    return new Circle();
}

Метод getShape вернет круг, но в этом вы, можно надеяться, ясно увидите, что вызов CircleMethod запрещен. Метод doSomething() может даже не знать, что существуют круги (например, потому что эти методы находятся в разных сборках), поэтому он может работать только с обработкой содержимого Shape как формы, независимо от того, что на самом деле внутри.

Я замечаю, что где-то в комментариях вы говорите, что вы ожидаете, что Circle circle = new Shape(); будет таким, каким он будет работать.

Надеюсь, вышеизложенное может объяснить, почему нет. Если нет, то, надеюсь, поможет другая аналогия.

Другие говорили, что круг - это просто ссылка. Представьте, что это пульт дистанционного управления, где декларация сообщает вам, какие кнопки имеют пульт дистанционного управления. В моем оригинальном примере у нашего пульта дистанционного управления под названием theShape есть кнопки на нем для всех методов на Shape, так как это так объявлено. Когда вы нажимаете кнопку на пульте дистанционного управления, он вызывает метод на реальном объекте, на который он указывает. Возможно, вы считаете, что пульт дистанционного управления неполный, потому что есть много вещей, которые находятся на Circle, что у нас нет кнопок, но главное в том, что все кнопки управления будут работать, потому что Circle поддерживает их.

В примере Circle circle = new Shape(); наш пульт дистанционного управления имеет все кнопки для Circle, но ясно, когда мы нажимаем кнопку для CircleMethod, тогда объект Shape, на который мы указываем наш пульт, не знает, что делать. И вот почему это не работает.

Как вы хотите это сделать? Мой второй пример, вероятно, является хорошим примером того, почему. Вы можете получить Shape из другого метода (например, тот, который может читать пользовательский ввод, где они выбирают Circle или Rectangle), и все, что вы хотите сделать, это нарисовать выбранную фигуру для пользователя. Вам не нужно знать, какой из них они выбрали, потому что вы знаете, что они оба являются фигурами и что Shapes имеют метод Draw, поэтому вы можете просто называть это.

P.S. Я знаю, что некоторые из них очень похожи на вещи, сказанные в других местах, но я чувствовал, что они все сосредоточены на технических аспектах, где я пытался больше по аналогии с тем, как это работает, чем технические вещи, такие как кучи и стеки, которые я не делаю много заботясь о большинстве случаев, хотя я считаю себя хорошим программистом.; -)

Ответ 11

Прежде всего, существует разница между типами значений и ссылочными типами. Когда вы объявляете ссылочный тип (например, экземпляр вашего класса Shape), в стеке выделяется память, которая будет содержать ссылку на слот памяти в куче.

Кроме того, объект типа будет создан в куче; как только вы инициализируете переменную Shape экземпляром Circle, объект Circle будет создан в куче, и он также будет иметь "указатель типа" к объекту Circle Type.

При вызове [override] virtual Draw метод CLR знает, какая реализация должна выполняться, потому что он может найти правильный тип с помощью указателя типа.

Более подробную информацию см. в этой статье.

Ответ 12

Память выделяется при выполнении new Circle(), поэтому у вас есть Circle в памяти!