Обратите внимание, что хотя в класс MiniVan никакие члены не добавлялись, в нем есть прямой доступ к открытому свойству Speed родительского класса; тем самым обеспечивается повторное использование кода. Такой подход намного лучше, чем создание класса MiniVan, который имеет те же самые члены, что и класс Car, скажем, свойство Speed. Дублирование кода в двух классах приводит к необходимости сопровождения двух порций кода, что определенно будет непродуктивным расходом времени.
Всегда помните о том, что наследование предохраняет инкапсуляцию, а потому следующий код вызовет ошибку на этапе компиляции, т.к. закрытые члены не могут быть доступны через объектную ссылку:
Console.WriteLine("***** Basic Inheritance *****\n");
...
// Создать объект MiniVan.
MiniVan myVan = new MiniVan;
myVan.Speed = 10;
Console.WriteLine("My van is going {0} MPH",
myVan.Speed);
// Ошибка! Доступ к закрытым членам невозможен!
myVan._currSpeed = 55;
Console.ReadLine;
В качестве связанного примечания: даже когда класс MiniVan определяет собственный набор членов, он по-прежнему не будет располагать возможностью доступа к любым закрытым членам базового класса Car. Не забывайте, что закрытые члены доступны только внутри класса, в котором они определены. Например, показанный ниже метод в MiniVan приведет к ошибке на этапе компиляции:
// Класс MiniVan является производным от Car.
class MiniVan : Car
{
public void TestMethod
{
// Нормально! Доступ к открытым членам родительского
// типа в производном типе возможен.
Speed = 10;
// Ошибка! Нельзя обращаться к закрытым членам
// родительского типа из производного типа!
_currSpeed = 10;
}
}
Замечание относительно множества базовых классов
Говоря о базовых классах, важно иметь в виду, что язык C# требует, чтобы отдельно взятый класс имел в точности один непосредственный базовый класс. Создать тип класса, который был бы производным напрямую от двух и более базовых классов, невозможно (такой прием, поддерживаемый в неуправляемом языке C++, известен как
// Недопустимо! Множественное наследование
// классов в языке C# не разрешено!
class WontWork
: BaseClassOne, BaseClassTwo
{}
В главе 8 вы увидите, что платформа .NET Core позволяет классу или структуре реализовывать любое количество дискретных интерфейсов. Таким способом тип C# может поддерживать несколько линий поведения, одновременно избегая сложностей, которые связаны с множественным наследованием. Применяя этот подход, можно строить развитые иерархии интерфейсов, которые моделируют сложные линии поведения (см. главу 8).
Использование ключевого слова sealed
Язык C# предлагает еще одно ключевое слово, sealed, которое предотвращает наследование. Когда класс помечен как sealed (запечатанный), компилятор не позволяет создавать классы, производные от него. Например, пусть вы приняли решение о том, что дальнейшее расширение класса MiniVan не имеет смысла:
// Класс Minivan не может быть расширен!
sealed class MiniVan : Car
{
}
Если вы или ваш коллега попытаетесь унаследовать от запечатанного класса MiniVan, то получите ошибку на этапе компиляции:
// Ошибка! Нельзя расширять класс, помеченный ключевым словом sealed!
class DeluxeMiniVan
: MiniVan
{
}
Запечатывание класса чаще всего имеет наибольший смысл при проектировании обслуживающего класса. Скажем, в пространстве имен System определены многочисленные запечатанные классы, такие как String. Таким образом, как и в случае MiniVan, если вы попытаетесь построить новый класс, который расширял бы System.String, то получите ошибку на этапе компиляции:
// Еще одна ошибка! Нельзя расширять класс, помеченный как sealed!
class MyString
: String
{
}