none
Наследование в C# RRS feed

  • Вопрос

  • Добрый день,

    у меня есть простой кусок кода:

     public class Class1
        {
            public virtual void Func1()
            {
                MessageBox.Show("Class1");
            }
        }
    
        public class Class2 : Class1
        {
            public void Func1()
            {
                MessageBox.Show("Class2");
            }
        }


    Почему когда я выполняю

    Class1 c = new Class2();
    c.Func1();

    то у меня вызывается метод из базового класса? Если я сделаю override метода в Class2, то тот же самый вызов тогда у меня начинает вызывать метод из класса наследника. 

    Объясните причину подобного поведения или дайте ссылку где можно прочитать про эту особенность?

    21 января 2013 г. 7:08

Ответы

  • Class1 c = new Class2();
    Тут написано:
    Создать элемента класса1, и дополнить с помощью конструктора класса2.
    (сначала же всё равно базовый конструктор сначала вызывается).

    Далее
    c.Func1();

    Вызывать Func1() элемента класса1 (по типу переменной c).
    Заодно смотрится нет ли перегрузок (а их нет, так как нет override).

    Вот если написать Class2 c = new Class2();
    то и будет вызываться метод класса2


    • Изменено INFEL8 21 января 2013 г. 8:33
    • Помечено в качестве ответа Abolmasov Dmitry 24 января 2013 г. 11:38
    21 января 2013 г. 8:32
  • override (Справочник по C#)

    public class Class2 : Class1
        {
            public override void Func1()
            {
                MessageBox.Show("Class2");
            }
        }





    • Изменено Kirill Bessonov 21 января 2013 г. 7:33
    • Помечено в качестве ответа Abolmasov Dmitry 24 января 2013 г. 11:38
    21 января 2013 г. 7:31
  • INFEL8, ты думаешь? А по-моему создаётся объект класса Class2 (ведь выполняется его конструктор), а затем происходит неявное приведение ссылки (implicit upcast) к базовому классу, и эта приведённая ссылка сохраняется в переменной типа Class1.
    А далее —  всё почти как ты и описал. Но с одной оговоркой: по ссылке по-прежнему хранится объект типа Class2. И на него можно создать ссылку этого типа и по ней обратиться к его специфичным методам:

    Class2 d;
    d = (Class2)c;
    d.Func1();

    Кроме того, конструкторы базового класса доступны из производного, но никогда автоматически не наследуются и автоматически не вызываются. Только явным образом при помощи ключевого слова base. Вот в случае явного вызова, конструктор базового класса действительно отработает в первую очередь. А затем дополнится.

    • Изменено A.Mur 21 января 2013 г. 10:22
    • Помечено в качестве ответа Abolmasov Dmitry 24 января 2013 г. 11:38
    21 января 2013 г. 10:14

Все ответы

  • override (Справочник по C#)

    public class Class2 : Class1
        {
            public override void Func1()
            {
                MessageBox.Show("Class2");
            }
        }





    • Изменено Kirill Bessonov 21 января 2013 г. 7:33
    • Помечено в качестве ответа Abolmasov Dmitry 24 января 2013 г. 11:38
    21 января 2013 г. 7:31
  • Этот называется "полиморфизм", и является одним из важнейших понятий ООП.
    21 января 2013 г. 7:50
    Модератор
  • Я в курсе про override, мне непонятно поведение именно

    Class1 c = new Class2();
    c.Func1();

    почему без override вызывается метод базового класса, если мы инстанциируем класс - наследник?

    21 января 2013 г. 8:07
  • Я же уже ответил - полиморфизм.
    21 января 2013 г. 8:09
    Модератор
  • Class1 c = new Class2();
    Тут написано:
    Создать элемента класса1, и дополнить с помощью конструктора класса2.
    (сначала же всё равно базовый конструктор сначала вызывается).

    Далее
    c.Func1();

    Вызывать Func1() элемента класса1 (по типу переменной c).
    Заодно смотрится нет ли перегрузок (а их нет, так как нет override).

    Вот если написать Class2 c = new Class2();
    то и будет вызываться метод класса2


    • Изменено INFEL8 21 января 2013 г. 8:33
    • Помечено в качестве ответа Abolmasov Dmitry 24 января 2013 г. 11:38
    21 января 2013 г. 8:32
  • INFEL8, ты думаешь? А по-моему создаётся объект класса Class2 (ведь выполняется его конструктор), а затем происходит неявное приведение ссылки (implicit upcast) к базовому классу, и эта приведённая ссылка сохраняется в переменной типа Class1.
    А далее —  всё почти как ты и описал. Но с одной оговоркой: по ссылке по-прежнему хранится объект типа Class2. И на него можно создать ссылку этого типа и по ней обратиться к его специфичным методам:

    Class2 d;
    d = (Class2)c;
    d.Func1();

    Кроме того, конструкторы базового класса доступны из производного, но никогда автоматически не наследуются и автоматически не вызываются. Только явным образом при помощи ключевого слова base. Вот в случае явного вызова, конструктор базового класса действительно отработает в первую очередь. А затем дополнится.

    • Изменено A.Mur 21 января 2013 г. 10:22
    • Помечено в качестве ответа Abolmasov Dmitry 24 января 2013 г. 11:38
    21 января 2013 г. 10:14
  • А у меня что-то вызываются
    а через base(..) выбираю какую перегрузку вызвать
    (ну или обязательно выбрать, если нет конструктора без параметров, а при том есть с параметрами)
    , наверное я не так как-то пробовал (всякое бывает, я часто ерунду пишу)?

    Что конкретно там где хранится не знаю, не читал CIL.
    Я читаю что по языку написано.
    А там вроде бы тип переменной всегда указывается слева от имени переменной.
    или если var написать, то == получить тип справа от присваивания.

    И получается, что если сделать
    Object test1 = new SuperPuperClass();
    без явного приведения к этому классу, будут вызываться методы класса Object,
    если нет override, конечно.

    сам не пробовал, но так думаю.

    Вернёмся к 1-му.
    Может там и хранится объект класса2,
    но всё равно же указывается как работать с переменной через присваивания типа ей.

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

    А при инициализации Вы указываете сколько памяти нужно держать для этого объекта.

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




    • Изменено INFEL8 21 января 2013 г. 13:01
    21 января 2013 г. 12:47
  • Да, аналогия с указателями — точная.
    Тип переменной для ссылочных типов (таких как классы) определяет только тип ссылки. Поэтому всё же полезно понимать, на какой именно тип эта ссылка указывает, чтобы не сталкиваться с искусственными ограничениями.
    И по поводу конструкторов: да, вы правы, конструктор без параметров базового класса неявно всё-таки вызывается (если он есть и если base не используется).
    То есть моё утверждение относится к случаю, если в базовом классе конструктора без параметров нет (в том числе и неявно создаваемого), и в производном классе неизбежно приходится создавать какой-нибудь конструктор и в нём явно использовать base().

    21 января 2013 г. 15:43
  • Как уже сказали в C# необходимо использовать специальный метод override для того, чтобы во время исполнения программы могло работать позднее связывание и класс, метод которого должен быть вызван определялся динамически.

    Без оператора override вы просто перекрыли реализацию функции для конкретного типа Class2, и только переменные этого типа будут вызывать данный метод. virtual - говорит о том, что метод может быть переопределен, override переопределяет метод.


    Для связи [mail]

    24 января 2013 г. 11:48
  • Чтобы было проще понять, можно воспользоваться дизассемблером ildasm.exe. В твоем случае твой код будет выглядеть так:

    .method private hidebysig static void  Main(string[] args) cil managed
    {
      .entrypoint
      // Code size       21 (0x15)
      .maxstack  1
      .locals init ([0] class ConsoleProject.Class1 c)
      IL_0000:  nop
      IL_0001:  newobj     instance void ConsoleProject.Class2::.ctor()
      IL_0006:  stloc.0
      IL_0007:  ldloc.0
      IL_0008:  callvirt   instance void ConsoleProject.Class1::Func1()
      IL_000d:  nop
      IL_000e:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
      IL_0013:  pop
      IL_0014:  ret
    } // end of method Program::Main

    Здесь видно, что вызывается непосредственно функция Class1::Func(), хотя создается экземпляр Class2. В случае вызова виртуального метода так же вызывается Class1::Func(), но тут берется указатель на реальный объект Class2, т.е. будет идти обращение к таблице методов типа Class2, а оттуда получение адреса метода Func1(). Если метод Func1() не переопределен в типе Class2, то слот Func1 в таблице методов типа Class2 будет указывать на то же самое место, что и слот Func1 в таблице методов типа Class1.


    • Изменено Higgs.Boson 25 января 2013 г. 20:17
    25 января 2013 г. 19:37
  • Вот и полный ответ на ваш вопрос, всё времени не было написать.
    10 марта 2013 г. 14:39
    Модератор