Настройка базовых и производных классов
Термины базовый класс и производный класс используются для описания связи между классами в иерархии наследования. Базовый класс используется для определения общего набора атрибутов и поведения, наследуемых другими классами. Производный класс используется для определения специализированного набора атрибутов и поведения, расширяющих или изменяющих базовый класс.
Изучение транзитивного характера наследования
В C#класс может наследоваться только от одного базового класса. Это ограничение называется одним наследованием. Одно наследование позволяет производной классу получить доступ к свойствам и методам одного базового класса, повышая четкую и простую иерархию. Концепция нескольких наследования не поддерживается в C#. Это ограничение позволяет избежать сложностей и неоднозначности, которые могут возникать при наследовании от нескольких базовых классов. Вместо этого C# использует интерфейсы для достижения аналогичных функциональных возможностей.
Хотя класс может наследоваться только от одного базового класса, наследование является транзитивным. Это означает, что если класс C наследует от класса B, а класс B наследует от класса A, то класс C наследует члены, объявленные как в классе B, так и в классе A. Это свойство позволяет более глубоким иерархиям и повторно использовать код.
Например, рассмотрим систему управления автомобилями, где у вас есть базовый класс Vehicle, производный класс Car, наследующий от Vehicle, и другой производный класс ElectricCar, наследующий от Car. В этой иерархии ElectricCar наследует члены как от Car, так и от Vehicle, демонстрируя транзитивный характер наследования.
public class Vehicle
{
public string Make { get; set; }
public string Model { get; set; }
public void StartEngine()
{
Console.WriteLine("Engine started.");
}
public void StopEngine()
{
Console.WriteLine("Engine stopped.");
}
}
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public void OpenTrunk()
{
Console.WriteLine("Trunk opened.");
}
public void HonkHorn()
{
Console.WriteLine("Horn honked.");
}
public void LockDoors()
{
Console.WriteLine("Doors locked.");
}
}
public class ElectricCar : Car
{
public int BatteryCapacity { get; set; }
public void ChargeBattery()
{
Console.WriteLine("Battery charging.");
}
public void DisplayBatteryStatus()
{
Console.WriteLine("Battery status displayed.");
}
}
public class CombustionEngineCar : Car
{
public int FuelCapacity { get; set; }
public void Refuel()
{
Console.WriteLine("Car refueled.");
}
public void CheckOilLevel()
{
Console.WriteLine("Oil level checked.");
}
}
В этом примере показана простая система управления автомобилями со следующими классами:
Vehicleкласс: базовый классVehicleсодержит общие свойства и методы для всех транспортных средств, таких как Make, Model, StartEngine и StopEngine.класс
Car: классCarнаследует отVehicleи добавляет определенные свойства и методы для автомобилей, напримерNumberOfDoors,OpenTrunk,HonkHornиLockDoors.класс
ElectricCar: классElectricCarнаследует отCarи добавляет свойства и методы, относящиеся к электрическим автомобилям, напримерBatteryCapacity,ChargeBatteryиDisplayBatteryStatus.класс
CombustionEngineCar: классCombustionEngineCarтакже наследует отCarи добавляет свойства и методы, относящиеся к автомобилям двигателя с горениями, напримерFuelCapacity,RefuelиCheckOilLevel.
В этом примере класс Car наследует элементы из класса Vehicle, а ElectricCar и CombustionEngineCar наследуют члены из класса Car. Эта иерархия демонстрирует транзитивный характер наследования, где ElectricCar и CombustionEngineCar наследуют члены как от Car, так и от Vehicle.
Проверка наследования элементов и видимости
Если класс наследует от базового класса, применяются следующие правила:
- Статические и экземплярные конструкторы не наследуются.
- Все остальные члены базового класса наследуются, но модификаторы доступа влияют на их видимость в производном классе. Модификаторы доступа:
public,protected,internalиprivate.
Общедоступные члены
Общедоступные члены доступны из любого кода, имеющего доступ к классу. Производные классы наследуют общедоступные члены, и они доступны вне иерархии классов.
public class BaseClass
{
public int publicField;
public void PublicMethod() { }
}
public class DerivedClass : BaseClass
{
public void AccessPublicMember()
{
publicField = 10;
PublicMethod();
}
}
В этом примере DerivedClass наследует элементы publicField и PublicMethod от BaseClass. Метод AccessPublicMember в DerivedClass может получить доступ к этим членам. Общедоступные члены также доступны из кода, который находится за пределами иерархии классов.
Защищенные элементы
Защищенные члены доступны в классе, в котором они объявлены и находятся в производных классах. Они недоступны вне иерархии классов.
public class BaseClass
{
protected int protectedField;
protected void ProtectedMethod() { }
}
public class DerivedClass : BaseClass
{
public void AccessProtectedMember()
{
protectedField = 10;
ProtectedMethod();
}
}
В этом примере DerivedClass наследует элементы protectedField и ProtectedMethod от BaseClass. Метод AccessProtectedMember в DerivedClass может получить доступ к этим членам. Однако при попытке получить доступ к защищенным членам из-за пределов иерархии классов возникает ошибка во время компиляции.
Внутренние члены
Внутренние члены доступны в одной сборке. Они недоступны вне сборки, даже если класс наследуется.
public class BaseClass
{
internal int internalField;
internal void InternalMethod() { }
}
public class DerivedClass : BaseClass
{
public void AccessInternalMember()
{
internalField = 10;
InternalMethod();
}
}
В этом примере DerivedClass наследует элементы internalField и InternalMethod от BaseClass. Метод AccessInternalMember в DerivedClass может получить доступ к этим членам, так как они входят в одну сборку. Однако при попытке получить доступ к внутренним членам извне сборки возникает ошибка во время компиляции.
Частные члены
Частные члены доступны только в классе, в котором они объявлены. Производные классы не наследуют частные члены, поэтому они не доступны напрямую в производном классе.
public class BaseClass
{
private int privateField;
private void PrivateMethod() { }
}
public class DerivedClass : BaseClass
{
public void AccessPrivateMember()
{
// Can't access privateField or PrivateMethod
}
}
В этом примере DerivedClass наследуется от BaseClass, но он не может получить доступ к элементам privateField или PrivateMethod, так как они являются частными.
Изучение использования абстрактных, виртуальных и запечатанных ключевых слов в базовом классе
Базовые и производные классы используют ключевые слова abstract, virtualи sealed для управления поведением наследуемых элементов. Эти ключевые слова позволяют определить уровень управления, который производные классы имеют над членами базового класса.
Изучение использования ключевого слова abstract
Ключевое слово abstract в C# используется для определения классов и членов классов, которые являются неполными и должны быть реализованы в производных классах. Абстрактный класс не может быть создан напрямую и предназначен для базового класса для других классов. Абстрактные методы и свойства объявляются без какой-либо реализации и должны быть переопределены в производных классах nonabstract.
Например:
public abstract class Shape
{
public abstract int GetArea();
}
public class Square : Shape
{
private int _side;
public Square(int side)
{
_side = side;
}
public override int GetArea()
{
return _side * _side;
}
}
class Program
{
static void Main()
{
Square square = new Square(5);
Console.WriteLine($"Area of the square = {square.GetArea()}");
}
}
В этом примере класс Shape является абстрактным и содержит абстрактный метод GetArea. Класс Square наследует от Shape и предоставляет реализацию для метода GetArea. Класс Square можно создать экземпляр, а метод GetArea возвращает область квадрата.
Следующие правила описывают, как ключевое слово abstract влияет на наследование:
- Абстрактные классы: абстрактный класс не может быть создан напрямую. Абстрактный класс является базовым классом для производных классов, а производные классы должны предоставлять реализации для всех абстрактных членов абстрактного класса.
- Абстрактные методы: абстрактные методы объявляются без какой-либо реализации в абстрактном классе. Производные классы должны переопределить эти методы и предоставить реализацию.
- Абстрактные свойства: как и абстрактные методы, абстрактные свойства объявляются без реализации и должны быть переопределены в производных классах.
Ключевое слово abstract в C# — это мощный инструмент для определения неполных классов и членов, которые должны быть реализованы в производных классах. Он применяет контракт, который должен соответствовать производным классам, обеспечивая реализацию определенных методов и свойств. Соответствующее использование ключевого слова abstract обеспечивает четкое распределение обязанностей между базовыми и производными классами.
Изучение использования ключевого слова virtual
Ключевое слово virtual в C# используется для определения методов и свойств, которые можно переопределить в производных классах. Виртуальный метод или свойство имеет реализацию в базовом классе, но его можно расширить или изменить в производных классах. Производные классы могут переопределить виртуальные члены для предоставления собственных реализаций.
Например:
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("Animal makes a sound");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Dog barks");
}
}
class Program
{
static void Main()
{
Animal animal = new Dog();
animal.MakeSound();
}
}
В этом примере класс Animal определяет виртуальный метод MakeSound, который печатает универсальное сообщение. Класс Dog наследует от Animal и переопределяет метод MakeSound для печати определенного сообщения. При создании объекта Dog и вызове метода MakeSound выполняется переопределенная реализация в классе Dog.
Следующие правила описывают, как ключевое слово virtual влияет на наследование:
- Виртуальные методы: виртуальный метод имеет реализацию в базовом классе, но его можно переопределить в производных классах.
- Виртуальные свойства: как и виртуальные методы, виртуальные свойства имеют реализацию в базовом классе и могут быть переопределены в производных классах.
Изучение использования ключевого слова sealed
Ключевое слово sealed в C# используется для предотвращения наследования или переопределения элемента класса или класса. Если класс помечается как sealed, его нельзя использовать в качестве базового класса для других классов. Если метод помечен как sealed, его нельзя переопределить в производных классах.
Например:
public class BaseClass
{
public virtual void Method1()
{
Console.WriteLine("Method1 in BaseClass");
}
public virtual void Method2()
{
Console.WriteLine("Method2 in BaseClass");
}
}
public class DerivedClass : BaseClass
{
public sealed override void Method1()
{
Console.WriteLine("Method1 in DerivedClass");
}
public override void Method2()
{
Console.WriteLine("Method2 in DerivedClass");
}
}
public class FinalClass : DerivedClass
{
// This class can't override Method1 because it's sealed in DerivedClass
public override void Method2()
{
Console.WriteLine("Method2 in FinalClass");
}
}
В этом примере DerivedClass наследует от BaseClass и переопределяет метод Method1, помечая его как sealed. Метод Method2 также переопределяется в DerivedClass, но не запечатан.
FinalClass наследует от DerivedClass и пытается переопределить метод Method2. Однако он не может переопределить метод Method1, так как он запечатан в DerivedClass.
Следующие правила описывают, как ключевое слово sealed влияет на наследование:
- Запечатанные классы: закрытый класс нельзя использовать в качестве базового класса для других классов. Он запрещает наследование от запечатаемого класса.
- Запечатанные методы: не удается переопределить запечатанный метод в производных классах. Он предотвращает дальнейшее изменение метода в производных классах.
- Запечатанные свойства: как и в запечатанных методах, не могут быть переопределены в производных классах.
Запечатанные классы и методы полезны, если требуется предотвратить дальнейшее расширение или изменение класса или метода. Они предоставляют способ ограничить наследование и убедиться, что некоторые члены остаются неизменными.
Проверка неявного наследования из объекта
В C#все классы неявно наследуются от класса Object. Класс Object определяет несколько методов, доступных для всех классов, таких как ToString, Equalsи GetHashCode. Если класс явно не наследуется от другого класса, он по-прежнему наследуется от Object по умолчанию.
-
ToString: методToStringвозвращает строку, представляющую текущий объект. По умолчанию он возвращает полное имя класса. -
Equals: методEqualsсравнивает два объекта для равенства. По умолчанию он сравнивает ссылки на объекты. -
GetHashCode: методGetHashCodeвозвращает хэш-код для текущего объекта. По умолчанию он возвращает хэш-код ссылки объекта.
Рассмотрим следующий пример кода, который создает три объекта Person и демонстрирует унаследованные ToString, Equalsи методы GetHashCode:
Person person1 = new Person { Name = "Alice", Age = 30 };
Person person2 = new Person { Name = "Alice", Age = 30 };
Person person3 = person1;
Console.WriteLine(person1.ToString());
Console.WriteLine(person1.Equals(person2));
Console.WriteLine(person1.GetHashCode());
Console.WriteLine(person1.Equals(person3));
public class Person
{
public string? Name { get; set; }
public int Age { get; set; }
}
public class Employee : Person
{
public int EmployeeNumber { get; set; }
public decimal Salary { get; set; }
}
// Output: Person
// False
// 32854180
// True
В этом примере кода метод ToString возвращает полное имя класса Person, который включает определенное пространство имен. Первый метод Equals сравнивает ссылки на объекты person1 и person2 и возвращает False. Метод GetHashCode возвращает хэш-код ссылки person1 объекта. А затем метод Equals сравнивает ссылки на объекты person1 и person3 и возвращает True.
Сводка
Наследование позволяет производным классам наследовать члены базового класса, определяющие общий набор атрибутов и поведения. Производные классы могут расширять или изменять поведение базового класса, добавляя новые члены или переопределяя существующие элементы. Ключевые слова abstract, virtualи sealed используются для управления наследованием или переопределением элементов базового класса. Видимость унаследованных элементов зависит от модификаторов доступа, таких как public, protected, internalи private. Все классы в C# неявно наследуются от класса Object, который предоставляет несколько методов, таких как ToString, Equalsи GetHashCode, доступные всем классам.