Страницы

Поиск по вопросам

вторник, 9 апреля 2019 г.

Семантика работы\хранения UpCast“инга \ DownCast”инга в CLR

Начнем с теории. Допустим,имеется следующие классы:
class A{} class B : A{} class C : B{}
Далее,мы делаем UpCast :
A a1 = new C();
Будет ли следующее утверждение верным : объект a1 является объектом типа C,и базовым классом для него является тип А (то бишь вверх по иерархии) !?
Далеко не уходя от кассы, представьте что добавили в код следующее:
class A { public virtual void Method() { Console.WriteLine("Method A invoked"); } } class B : A { public new virtual void Method() { Console.WriteLine("Method B invoked"); } } class C : B { public override void Method() { Console.WriteLine("Method C invoked"); } }
Что будет выведено на экран ? В первую очередь покажется,что тут все очевидно,и вывод выходе получим :
Method A invoked Method A invoked Method С invoked Method C invoked
Но на самом то деле,мы получим : Method A invoked Method A invoked Method A invoked Method C invoked
Исходя из этой логики,выходит что мое предыдущее высказывание не верное,и это значит,что все таки базовым классом для C,является B ?

Теперь перейдем к другой части вопроса. К примеру имеем код:
class Program { static void Main(string[] args) { //объект типа класса А A a = new A(); //объект типа класса B B b = new B(); //UpCast, который равен объекту "b" A a1 = b; //UpCast как отдельный объект A a2 = new B(); //DownCast, который равен объекту "а1" B b1 = (B)a1;
B b2 = a as B; // вернет Null, т.к. DownCast //без предварительного UpCast не возможен
// B b2 = new A(); - невозможно из за безопасности типов
//сравниваем b с а1,видим что типы идентичны. Console.WriteLine(b.GetType() == a1.GetType()); //сравниваем а2 с а1,видим что типы идентичны. Console.WriteLine(a2.GetType() == a1.GetType()); //сравниваем b1 и а1,видим что типы идентичны Console.WriteLine(b1.GetType() == a1.GetType());
//Проверяем сами обьекты,вернет True Console.WriteLine(a1.Equals(b)); //вернет False,но реализация этих объектов идентична Console.WriteLine(a2.Equals(a1)); //Вернет True Console.WriteLine(b1.Equals(a1));
Console.ReadKey(); } } class A { } class B : A { }

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


Ответ

По порядку:
Будет ли следующее утверждение верным : объект a1 является объектом типа C,и базовым классом для него является тип А (то бишь вверх по иерархии) !?
Нет. Корректным утверждением будет следующее: объект a1 является объектом типа C,и базовыми классами для него являются типы B и А
Разница большая, поскольку каждый тип в иерархии наследования может привносить новые аспекты поведения.
Теперь дальше:
class A { public virtual void Method() { Console.WriteLine("Method A invoked"); } } class B : A { public new virtual void Method() { Console.WriteLine("Method B invoked"); } } class C : B { public override void Method() { Console.WriteLine("Method C invoked"); } }
А данном примере сложно сказать, что именно хотел сказать автор этих строк с точки зрения бизнес-логики, но звучит это примерно так: класс B добавляет новый метод Method, но, к сожалению, он использует метод, имя которого уже есть в базовом классе. Но класс B хочет не "подменить" поведение метода из базового класса, а создать свой собстенный метод, который ничего не имеет общего с методом базового класса, кроме имени.
Подобная практика приводит к неоднозначному поведению, поскольку теперь выбор метода определяется не только динамическим типом объекта (типом времени исполнения), но и типом переменной (типом, известным компилятору): если используется переменная типа A, то будет вызван метод из класса A. Если же тип переменной - это B или C, то будет использоваться другая ветка методов (ниже будет объяснение, почему это так).
Другими словами, с точки зрения метода Method существует две ветки: одна начинается типом А и им же и ограничивается, и есть другая полиморфная ветка, которая начинается в типе B и продолжается в наследнике - типе С
Теперь немного о том, как это устроено в CLR.
Для каждого типа CLR хранит табличку с методами (Method Table), где каждая запись описывает отельный метод - его сигнатуру и признак того, переопределяет ли данный слот метод из базового класса.
Когда вы объявили метод с приставкой new в классе B, CLR добавила "новый" метод в табличку, при этом пометила этот метод, как новый, не связанный с методом из базового класса. В случае же класса С, метод Method в табличке методово типа C помечен, как полиморфный, т.е. переопределяющий поведение непосредственного базового класса.
Теперь стоит сказать, как происходит разрешение метода во время исполнения: в случае вызова a.Method будет вначале определен статический тип переменной a, после чего в таблице методов будет найден метод Method. Если статический тип переменной - это A, то вначале будет просмотрена таблица методов типа A. И в этом случае CLR увидет, что этот метод виртуальный. После чего будет определен реальный тип объекта (например, тип C) и CLR посмотрит, а есть ли у этого типа переопределение метода, объявленного в типе A. CLR получит отрицательный ответ, поскольку тип C не переопределяет метод Method, объявленный в типе A (ведь этот тип переопределяет метод Method типа B).
Вот и получается, что результат разрешения имени метода у нас теперь зависит не только от типа времени исполнения, но и от типа переменной времени компиляции.

Комментариев нет:

Отправить комментарий