Беспорядок о виртуальном/новом/переопределять

Я немного смущен virtual/new/override вещь. Вот пример:

class A
{
    public virtual void mVVirtual() { Console.WriteLine("A::mVVirtual"); }
}

class B : A
{
    public virtual void mVVirtual() { Console.WriteLine("B::mVVirtual"); }
}

class C : B
{
    public override void mVVirtual() { Console.WriteLine("C::mVVirtual"); }
}


class Test
{
    static void Main()
    {
        B b1 = new C();
        b1.mVVirtual();    //C::mVVirtual ... I understand this

        A a2 = new C();
        a2.mVVirtual();    //A::mVVirtual ... ???
    }
}

Я не добираюсь, почему во втором вызове мы добираемся A::mVVirtual. Я обычно рассматриваю эти проблемы с этим "алгоритмом":

  1. Проверьте тип переменной, содержащей ссылку для объекта для названного метода экземпляра mVVirtual? Не имеет того..., но действительно имеет виртуальный метод с той подписью и именем!
  2. Виртуальный метод? Давайте затем проверим тип объекта, сохраненного a2 (C) для переопределения того метода. Это имеет один->, Выполняется C::mVVirtual!

Где мой "алгоритм" неправильно? Я действительно смущен этим и был бы очень признателен за некоторую справку.

8
задан Null 17 July 2015 в 19:47
поделиться

5 ответов

У вас есть предупреждения, скрытые? Когда я делаю то, что вы сделали, я получаю это предупреждение:

«Projectname.classname.b.mvirtual () скрывает унаследованный элемент 'projectname.classname.a.mvvirtual ()'. Чтобы сделать нынешний элемент переопределить эту реализацию, добавьте ключевое слово переопределения. В противном случае добавьте новое ключевое слово.

Если вы использовали , переопределяют в классе B, у вас не было бы этой проблемы; Оба случая дадут вам «C :: MVVIRTUAL». Поскольку вы не используете , переопределяют в классе B, есть неявное new перед способом. Это ломает цепочку наследства. Ваш код вызывает метод на типе A, и нет наследующих классов, которые переопределяют этот метод из-за неявки нового . Так что это должно назвать реализацией класса А.

3
ответ дан 5 December 2019 в 07:58
поделиться

Обновление: для получения дополнительной информации об этой функции языка см. В разделе «Дополнительный вопрос»: Подробнее о виртуальных / новых ... Плюс интерфейсов!

Ответ Джейсона правильный. Суммировать его немного более кратко.

У вас есть три метода. Позвоните им MA, MB и MC.

У вас есть два «коробка», или, как они обычно называют, слоты. Мы будем придерживаться номенклатуры Джейсона. Позвоните им box1 и box2.

«A» определяет box1.

«B» определяет Box2.

«C» определяет нет коробки; Он повторно использует box2.

Когда вы говорите «Новый a ()», Box1 заполнен MA.

Когда вы говорите «New B ()», Box1 заполнен MA и BOX2 заполняется MB.

Когда вы говорите «новый C ()», Box1 заполнен MA и BOX2, заполняется MC.

Теперь предположим, что у вас есть переменная типа A и вызов методу. Причина, как компилятор. Компилятор говорит: «Есть ли коробки на типе A, которые соответствуют этому имени?» Да, есть один: box1. Следовательно, компилятор генерирует вызов содержимое box1.

Как мы уже видели, содержимое box1 всегда считается ma, поэтому MA всегда вызывается независимо от того, является ли переменная на самом деле удерживает ссылку на A, B или C.

Теперь предположим, что у вас есть переменная Тип B и вызов методу. Опять же, думаю, как компилятор. Компилятор говорит: «Есть ли коробки на типе B, которые соответствует этому имени?» Да, есть два коробки, которые соответствуют по имени. Компилятор говорит: «Какой из этих двух более тесно связан с B?» Ответ в Box2, потому что B объявляет Box2. Следовательно, компилятор генерирует вызов в Box2.

Это позвонит MB, если переменная содержит B, потому что в B, Box2 содержит MB. Это позвонит MC, если переменная содержит C C, поскольку в C, Box2 содержит MC.

Это теперь ясно? Помните, Разрешение перегрузки просто выбирает коробку . Что содержимое коробки зависит от объекта во время выполнения.

6
ответ дан 5 December 2019 в 07:58
поделиться

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

Переопределение средства вы пишете метод, который собирается 'заменить' реализацию для виртуального или абстрактного метода (с тем же именем/подписью) выше цепочка наследования.

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

различие следующим образом

class base     { public virtual void  foo() { Console.write("base.foo"); } }
class derived  { public override void foo() { Console.write("derived.foo"); } }
base b = new base();
b.foo()  // prints "base.foo" // no issue b is a base and variable is a base
base b = new derived();
b.foo(); // prints "derived.foo" because concrete tyoe is derived, not base

, но

class base     { public void  foo() { Console.write("base.foo"); } }
class derived  { public new void foo() { Console.write("derived.foo"); } }
base b = new base();
b.foo()  // prints "base.foo" // no issue b is a base and variable is a base
base b = new derived();
b.foo(); // prints "base.foo" because variable b is base. 
derived d = b as derived;
d.foo()    //  prints "derived.foo" - now variable d is declared as derived. 

Ваша вторая печать вызова А:: mvVirtual, потому что тот метод ( mVVirtual) на самом деле не виртуальный, (несмотря на он - имя), потому что он имеет новый спецификатор... Таким образом, это решает на основе типа переменной, который является A.

Для объяснения, что продолжается технически Каждый Тип имеет "Таблицу метода" с указателями на все методы в том типе. (Это не экземпляр типа, который имеет эту таблицу, Это сам ТИП .) Таблица метода каждых Типов структурирована с весь доступный виртуальный методы сначала, от объект (дальше всего цепочка наследования) вначале, к виртуальным методам, объявленным в самом типе, в конце. Затем после того, как все виртуальные методы представлены, все невиртуальные методы добавляются, снова, из любых невиртуальных методов в объект , во-первых, полностью к любым невиртуальным методам в самом Типе. Таблица структурирована этот путь так, чтобы смещения для всех виртуальных методов были идентичны в таблицах метода всех производных классов, так как компилятор может назвать эти методы от переменных объявленными как другие типы, даже из кода в других методах объявленный и реализованный в базовых типах реального класса.

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

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

1
ответ дан 5 December 2019 в 07:58
поделиться

Это то, как я понимаю его

A - базовый класс
B наследует a, но не переопределяет его
C Наследовать B, но он переопределяет его

, так как вы объявляете A, но инициализируете C, он будет игнорировать переопределение, потому что базовый класс является A и A, никогда не переопределяйте из B.

0
ответ дан 5 December 2019 в 07:58
поделиться

Это называется топологической сортировкой .

Каноническое применение топологическая сортировка (топологическая order) находится в планировании последовательности задания или задачи; топологическая сортировка алгоритмы были впервые изучены в начало 1960-х годов в контексте PERT методика планирования в проекте управление (Ярнагин 1960). Задания представлены вершинами, и там является ребром от x до y, если задание x должно быть завершенным до выполнения задания y запущено (например, при стирке одежда, стиральная машина должна закончить, прежде чем положить одежду сухой). Затем топологическая сортировка дает порядок выполнения заданий.

-121--4746570-

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

Хотя сами объекты содержат примитивы, там место хранения находится место назначения фактического объекта, находящегося в куче.

Кроме того, что ваше заявление довольно хорошо написано. У вас есть конкретный вопрос, который я пропустил:)?

-121--2805577-

Вот как вы думаете о виртуальных методах. Каждый экземпляр класса имеет «поля» для хранения методов. Когда вы помечаете метод как виртуальный , он говорит сделать новый «ящик» и поместить в него метод. Когда метод помечается как override в производном классе, он сохраняет «box» из базового класса, но помещает в него новый метод.

Здесь имеется класс A и метод с именем mVVirtual , помеченный как virtual . Это означает, что создайте новый «ящик» с именем mVVirtual и поместите в него метод с определением

Console.WriteLine("A::mVVirtual"); 

Тогда у вас есть производный класс B и метод с именем mVVirtual , который помечен как virtual . Это говорит сделать новый «ящик» с именем mVVirtual и поместить в него метод с определением

Console.WriteLine("B::mVVirtual"); 

В частности, «ящик», унаследованный от A , скрыт! Его не могут видеть объекты, которые набраны как B , или классы, производные от B .

Имеется производный класс C и метод с именем mVVirtual , помеченный как override . Это говорит взять «ящик» с именем mVVirtual , унаследованный от B , и поместить в него другой метод с определением

Console.WriteLine("C::mVVirtual"); 

Теперь, когда у вас есть

B b1 = new C(); 
b1.mVVirtual();

вы говорите компилятору, что b1 является B так что b1.mVVirtual() ищет в «box» mVVirtual и находит метод с определением

Console.WriteLine("C::mVVirtual"); 

, поскольку b1 действительно является C , и именно это находится в «box» mVVirtual для экземпляров C .

Но когда у вас есть

A a2 = new C(); 
a2.mVVirtual();

вы говорите компилятору, что a2 является A и поэтому он смотрит в «поле» и находит

Console.WriteLine("A::mVVirtual");

Компилятор не может знать, что a2 действительно является C (вы ввели его как A ), поэтому не известно, что a2 действительно является экземпляром класса, производным от класса, который скрыл «box» mVVirtual , определенный A . Известно, что A имеет «box» с именем mVVirtual и поэтому выдает код для вызова метода в этом «box».

Таким образом, чтобы попытаться поставить это лаконично:

class A {
    public virtual void mVVirtual() { Console.WriteLine("A::mVVirtual"); }
}  

определяет класс, который имеет «box» с полным именем A:: mVVirtual , но что вы можете сослаться на mVVirtual .

class B : A 
{
    // "new" method; compiler will tell you that this should be marked "new" for clarity.
    public virtual void mVVirtual() { Console.WriteLine("B::mVVirtual"); }
}  

определяет класс, который имеет «поле» с полным именем B:: mVVirtual , но на который можно ссылаться под именем mVVirtual . Ссылка на B.mVVirtual не относится к «ящику» с полным именем A:: mVVirtual ; это «поле» не может быть отображено объектами, набранными как B (или классами, производными от B ).

class C : B
{
    public override void mVVirtual() { Console.WriteLine("C::mVVirtual"); }
}  

определяет класс, который принимает «box» с полным именем B:: mVVirtual и помещает в него другой метод.

Тогда

A a2 = new C(); 
a2.mVVirtual();

говорит, что a2 является A , так что a2.mVVirtual выглядит в «поле» с полным именем A:: mVVirtual и вызывает метод в этом «поле». Вот почему вы видите

A::mVVirtual

на консоли.

Существует два других аннотатора методов. аннотация делает новое «поле» не помещает определение метода в «поле». new создает новый «box» и помещает определение метода в «box», но не позволяет производным классам поместить свои собственные определения метода в «box» (используйте virtual , если вы хотите это сделать).

Извините за долголетие, но я надеюсь, что это поможет.

11
ответ дан 5 December 2019 в 07:58
поделиться
Другие вопросы по тегам:

Похожие вопросы: