Начнем с теории.
Допустим,имеется следующие классы:
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).
Вот и получается, что результат разрешения имени метода у нас теперь зависит не только от типа времени исполнения, но и от типа переменной времени компиляции.
Комментариев нет:
Отправить комментарий