Ще се спрем отново на понятието наследяване поради особената му важност в обектно-ориентираното програмиране. Няма да обясняваме теоретичната страна наследяването, тъй като това е извън обхвата на настоящата тема. Ще обясним само как да извършваме наследяване на класове със средствата на езика C#.
В C# синтаксиса и семантиката на наследяването са близки до тези в други езици за обектно-ориентирани езици, като C++ и Java. За да направим даден клас Derived наследник на даден друг клас Base, трябва след декларацията на класа Derived да сложим двоеточие, следвано от името на класа Base. За да илюстрираме това, ще разширим един от примерите, които разгледахме по-горе в темата:
class Student
{
private string mName;
private int mStudentId;
private string mPosition = "Student";
public Student(string aName, int aStudentId)
// ...
public Student(string aName) : this(aName, -1)
// ...
public void PrintName()
{
Console.WriteLine("Student name: {0}", mName);
}
}
public sealed class Kiro : Student
{
public Kiro() : base("Бай Киро", 12345)
{
}
public void Oversleep()
{
//...
}
static void Main()
{
Student tosho = new Student("Тошо", 54321);
Kiro kiro1 = new Kiro();
Student kiro2 = new Kiro();
// Kiro kiro3 = new Student("Бай Киро", 12345); // invalid!
tosho.PrintName();
kiro1.PrintName();
// kiro2.Oversleep();
((Kiro)kiro2).Oversleep();
}
}
|
Виждаме, че класът Kiro наследява класа Student, с което приема от него всички негови полета, свойства, методи и други членове. Разбира се, наследените членове са достъпни за класа Kiro, само ако не са били обявени като private в базовия клас Student.
Трябва да обърнем внимание на третия ред от метода Main(…):
Student kiro2 = new Kiro();
|
В него създаваме обект от тип Kiro, но го присвояваме на променлива от тип Student. Тази операция е напълно коректна, тъй като присвояването на обект от наследен тип в променлива от базов тип е позволено. Обратното, обаче, не е в сила и ако разкоментираме втория ред, приложението не би се компилирало.
Обръщението kiro1.PrintName() е също напълно валидно, тъй като класът Kiro наследява всички членове на базовия клас Student и затова съдържа дефиницията на метода PrintName().
Класове, които не могат да се наследяват (sealed)
В дефиницията на класа Kiro забелязваме употребата на ключовата дума sealed. С нея указваме, че Kiro не може да бъде наследяван от друг клас. Това е пример как чрез забраняването на наследяване можем да създаваме йерархии от класове по-близки до реалните обекти, които представяме. В конкретния пример е удачно да маркираме класа като sealed, тъй като той представлява категория, която не може повече да се конкретизира (Kiro е клас, който съответства на един конкретен обект от действителността, а не на група различни обекти).
Наследяване при структурите
В някои обектно-ориентирани езици, като например C++, се допуска наследяване на структури. В C# и в другите .NET езици това не е позволено.
|
Структурите в .NET Framework не могат да се наследяват по между си и не могат да наследяват и да бъдат наследявани от класове.
|
Нека направим един прост експеримент, за да онагледим невъзможността за наследяване на структури. Със следния код ще създадем една тривиална структура:
Отново с помощта на инструмента ildasm получаваме MSIL кода за тази проста структура:
Забелязваме, че структурата TestStruct наследява от System.ValueType и, което в нашия случай е по-интересно, в дефиницията й фигурира модификаторът sealed. Това указва на компилатора, че този тип не може да бъде наследен. Следната ситуация, при която се опитваме да наследим структура от клас, е също недопустима и предизвиква грешка при опит за компилация:
public class TestClass
{
}
public struct AnotherTestStruct : TestClass
{
}
|
Нека сега разгледаме конвертирането (casting) на обект от даден тип към обект от друг тип. При класове в отношение наследник-наследен можем да конвертираме нагоре по йерархията (upcasting) и надолу по йерархията (downcasting). Нека обясним тези две понятия.
Конвертиране нагоре (upcasting)
С операцията Student kiro2 = new Kiro() от по-горния пример присвояваме обект от клас Kiro на променлива от клас Student, т.е. конвертираме (преобразуваме) обекта към класа Student. В този случай използваме конвертиране нагоре (upcasting), тъй като Student е базов клас на Kiro или, иначе казано, се намира по-горе в йерархията. Тази операция е напълно допустима, тъй като kiro2 действително е студент.
В нашия пример следващият ред
Kiro kiro3 = new Student("Бай Киро", 12345);
|
е коментиран, тъй като операцията, която там се опитваме да извършим, е недопустима и този код не би могъл да се компилира, тъй като обектът, който конструираме посредством new Student("Бай Киро", 12345) не е инстанция на класа Kiro (въпреки че го наподобява по стойностите на полетата, той не съдържа метода Oversleep()).
Конвертиране надолу (downcasting)
С обръщението (Kiro)kiro2 разглеждаме обекта kiro2 като обект от тип Kiro. Тази операция наричаме конвертиране надолу, или downcasting. Типът на израза в скобите е Kiro и заради това можем свободно да извикаме метода Oversleep(), защото въпреки, че е сочен от променлива от тип Student, този израз фактически е инстанция на класа Kiro и съдържа имплементация на метода. На долната илюстрация виждаме, че и Visual Studio .NET разпознава типа на израза като ни предоставя членовете му в падащото меню за автоматично завършване на израза:
|
В C# конвертирането надолу е синтактично валидна операция, независимо дали обектът, който конвертираме, е действително от въпросния наследяващ типа. Например, закоментираното обръщение Kiro kiro3 = new Student("Бай Киро", 12345) би могло да се зададе във вида Kiro kiro3 = (Kiro)new Student("Бай Киро", 12345), което се компилира успешно от C# компилатора без дори да генерира предупреждение, тъй като по време на компилация не е известно дали типовете са съвместими. При изпълнението на този код, обаче, въпросното преобразуваме ще предизвика изключение System.InvalidCastException, тъй като конструираният обект не е от тип Kiro или съвместим с него тип.
|
Сподели с приятели: |