Polymorphism


1
 Здравейте, имам няколко въпроса. Нека имаме абстрактен клас Animal с абстрактни методи, и наследник Dog с имплементация. Нека имаме Animal dogBig = new Dog() и Dog dogSmall = new Dog(). Да направим и колекция List<Animal> animals = new List<Animal>(). Въпроса ми е защо компилаторът позволява и двата обекта да ги вкарам в листа? animals.Add(dogBig) и animals.Add(dogSmall). И третира ли ги по някакъв различен начин спрямо начинът им на създаване? Веднъж вкаран вътре в листа Dog dogSmall = new Dog(), става ли еквивалентен на другите обекти в листа които са инициализирани през базовия клас. И при извикване на методите при обхождане на листа компилаторът директно ли вика неговия метод или минава през базовия клас и съответно с виртуални таблици и пойнтъри търси конкретния метод както на dogBig? А когато създаваме Animal dogBig = new Dog() , компилатирът(compile-time) го третира като Animal, но всъщност знае че е Dog преди дори да викаме негови функции(run-time), но просто не ни позволява да ползваме директно методите му(compile-time), така ли е?

в C# OOP от dobri19 (45 точки)


Отговори



2

Когато създаваш колекция от елементи, то тези елементи трябва да имат общи черти. Когато ги разглеждаш като елемент от колекцията, ти можеш да ползваш само такива техни характеристики, които са общи.

Имаш колекция от животни. Всички животни могат да бъдат хранени. Ако имаш списък с животни, за всеки един техен елемент ти ще можеш да викаш само метода "хранене".

В този списък обаче можеш да слагаш "ездитни животни", можеш да слагаш и "декоративни животни". Ездитните могат да имат метод "яздене", декоративните - някакъв друг метод. Но докато ти ги разглеждаш като абстрактния клас животни, няма да можеш да правиш присъщите само за даден подклас действия. Трябва да ги cast-неш към конкретния подвид, и тогава ще можеш да ги ползваш пълноценно.

Можеш да си направиш нова колекция от тип "ездитни животни" и от голямата колекция "животни" да подбереш само ездитните, и тогава върху всеки елемент от новата колекция ще можеш да извършваш действието "Яздене".

Вътрешно как се представят нещата не ми е много ясно... а и едва ли има голямо значение. Компилатора се грижи да не можеш да ползваш метод, за който не е гарантирано че ще е присъщ за всеки елемент от колекцията. По този начин те предпазва от викане на методи, които може да не са имплементирани от някой от елементите. Но вътрешно той си знае всеки един от елементите точно от какъв клас е, и при нужда може да го "разпакетира" до конкретния клас (стига да има йерархична свързаност).

Примерно "бенгалски тигър" е както бенгалски тигър, така е и тигър, така е и от семейство "котки", така е и бозайник, така е и животно. Можеш да го слагаш в колекция от който искаш от "бащините" му класове, но във всеки от тях ще можеш да се ползваш само от методите, присъщи за този клас. Смесиш ли бенгалски тигър и кон в колекция "животни", няма да имаш достъп до метода "яздя", нито до "ловувам плячка", защото те са методи присъщи за по-конкретен клас. А не можеш да сложиш кон в колекция от котки или от тигри - липсва му интерфейса "ловувам плячка". Но пък домашна котка и бенгалски тигър стават да се сложат в колекция от "котки", защото и двете ловуват... 

За викането на методите - при извикването на метод от по-обща колекция компилатора търси от по-конкретния към по-общия клас. Първо ще види дали има обяснено как бенгалския тигър ловува. Ако няма - ще погледне дали знае как ловоува какъвто и да е тигър. Ако и там няма - ще потърси да види как ловуват котките... Тази проверка се прави един път при компилиране - за да е сигурно че винаги ще има такъв метод, независимо на "кое ниво на стълбицата", така и по време на изпълнение - за да се намери метод който е най-близо до този на конкретния екземпляр.

Ако имаш метод за ловуване описан в "бенгласки тигър" и в "котки", ако вземеш бенгалски тигър и му кажеш "ловувай", ще се ползва метода на бенгалския тигър. Обаче ако имаш амурски тигър, за който не е описано как ловува, ще се погледне първо за "някакъв тигър", там няма да има и ще се вземе метода "ловувай" от по-базовия клас "котки".


от JulianG (5316 точки)


1

new Dog() винаги ще създаде нов Dog обект, независимо като какъв го ползваш. 

Когато на променливата и кажеш Animal bigDawg, го изпозлваш като Animal, но обекта се запазва.

Промяна в поведение би имала, ако в наследяващия клас криеш пропъртита/ методи на базовия, тогава няма да влезеш в криещия код когато достъпваш обекта през базовия му клас въпреки че референцията е към същия обект. Но това по принцип е доста голяма простотия да се прави afaik. В стандартния случай поведението се запазва.

// Copy Code Snippet Here using System; namespace ConsoleApplication1 { class Animal { public string Name { get { return "Animal"; } } } class Dog : Animal { public new string Name { get { return "Dog"; } } } class Program { static void Main(string[] args) { var dawg = new Dog(); Console.WriteLine(dawg.Name); var animalDawg = dawg as Animal; Console.WriteLine(animalDawg.Name); Console.WriteLine(animalDawg == dawg); } } }


от todorovh (2055 точки)