C# - 继承
C# - 继承
Section titled “C# - 继承”继承是面向对象编程(OOP)的基本支柱。它允许一个类继承另一个类(称为基类或父类)的字段和方法。继承的类称为派生类(或子类)。这种机制促进了代码的复用性,有助于建模现实世界中的“is-a”(是)关系,从而创建更具组织性和可维护性的应用程序。
通过使用继承,你可以创建一个定义通用功能的通用基类,然后创建更具体的派生类来添加或修改这些功能。例如,Car(汽车)是一种Vehicle(交通工具),而Bicycle(自行车)是一种Vehicle。Vehicle将是基类,而Car和Bicycle将是派生类。
基类和派生类
Section titled “基类和派生类”在 C# 中,一个类只能从一个基类继承(单继承),但可以实现多个接口。这种设计避免了与多重类继承相关的复杂性和歧义问题,例如“菱形问题”。
创建派生类的语法很简单。你指定派生类名,后跟一个冒号(:),然后是基类名。
// 基类public class BaseClass{ // 基类的成员}
// 派生类public class DerivedClass : BaseClass{ // 派生类的成员}让我们使用现代 C# 属性和构造函数,对经典的 Shape(形状)和 Rectangle(矩形)示例进行现代化改造。属性提供了一种比公共字段或简单的 getter/setter 方法更灵活、更安全的数据暴露方式。
using System;
nnamespace InheritanceExample{ // 带属性的基类 public class Shape { // 宽度和高度的自动实现属性 public double Width { get; set; } public double Height { get; set; }
public void DisplayDimensions() { Console.WriteLine($"Width: {Width}, Height: {Height}"); } }
// 派生类 public class Rectangle : Shape { // Rectangle 特有的方法 public double GetArea() => Width * Height; }
public class Program { public static void Main(string[] args) { var rect = new Rectangle();
// 设置从 Shape 继承的属性 rect.Width = 5.5; rect.Height = 3.0;
rect.DisplayDimensions();
// 调用派生类的方法 Console.WriteLine($"Area: {rect.GetArea()}"); } }}当上述代码被编译并执行时,会产生以下结果:
Width: 5.5, Height: 3.0Area: 16.5初始化基类构造函数
Section titled “初始化基类构造函数”派生类构造函数必须调用其基类的构造函数。如果你不显式调用基类构造函数,编译器将尝试调用基类的默认(无参数)构造函数。要调用特定的基类构造函数,你需要在派生类的构造函数声明中使用 base 关键字。
这是一个示例,展示了 Tabletop 类如何使用构造函数初始化其 Rectangle 基类。
using System;
namespace BaseInitialization{ public class Rectangle { public double Length { get; } public double Width { get; }
// 基类构造函数 public Rectangle(double length, double width) { Length = length; Width = width; }
public double GetArea() => Length * Width;
public virtual void Display() { Console.WriteLine($"Length: {Length}"); Console.WriteLine($"Width: {Width}"); Console.WriteLine($"Area: {GetArea()}"); } }
public class Tabletop : Rectangle { public decimal CostPerSquareUnit { get; set; }
// 调用基类构造函数的派生类构造函数 public Tabletop(double length, double width, decimal costPerSquareUnit) : base(length, width) { CostPerSquareUnit = costPerSquareUnit; }
public decimal GetTotalCost() => (decimal)GetArea() * CostPerSquareUnit;
// 覆写 Display 方法以添加更多信息 public override void Display() { base.Display(); // 调用基类的 Display 方法 Console.WriteLine($"Total Cost: {GetTotalCost():C}"); // :C 格式化为货币 } }
public class Program { public static void Main(string[] args) { var table = new Tabletop(4.5, 7.5, 70m); // 'm' 后缀表示 decimal 类型 table.Display(); } }}当上述代码被编译并执行时,会产生以下结果:
Length: 4.5Width: 7.5Area: 33.75Total Cost: $2,362.50派生类可以提供其从基类继承的方法的自己的实现。要覆写一个方法,基类方法必须用 virtual、abstract 或 override 关键字标记。然后,派生类方法必须使用 override 关键字。
这个示例演示了多态性,其中一个 Animal 变量可以持有 Dog 对象,并且调用 MakeSound() 会执行 Dog 类中被覆写的方法。
using System;
public class Animal{ public virtual void MakeSound() { Console.WriteLine("Some generic animal sound"); }}
public class Dog : Animal{ public override void MakeSound() { Console.WriteLine("Woof! Woof!"); }}
public class Cat : Animal{ public override void MakeSound() { Console.WriteLine("Meow"); }}
public class Program{ public static void Main() { Animal myAnimal = new Animal(); Animal myDog = new Dog(); // Dog 是一种 Animal Animal myCat = new Cat(); // Cat 是一种 Animal
myAnimal.MakeSound(); myDog.MakeSound(); myCat.MakeSound(); }}执行后,该程序会输出:
Some generic animal soundWoof! Woof!Meow使用 sealed 阻止继承
Section titled “使用 sealed 阻止继承”你可以通过用 sealed 关键字标记一个类来阻止它被用作基类。这对于不打算被扩展的类很有用。例如,.NET 中的 string 类就是 sealed 的。
public sealed class FinalClass{ public void ShowMessage() { Console.WriteLine("This class cannot be inherited."); }}
// 以下行会导致编译错误:// public class AttemptToDerive : FinalClass { } // 错误: “AttemptToDerive”: 无法从密封类型“FinalClass”派生通过接口实现多重继承
Section titled “通过接口实现多重继承”尽管 C# 不支持从多个类继承,但一个类可以实现多个接口。接口定义了一个契约——一组实现类必须提供的公共方法和属性。这允许你以安全灵活的方式实现多态性和代码复用。
最佳实践: 当你只需要定义一组能力契约时(例如 ISerializable、IComparable),优先考虑实现接口而非类继承。
在这里,ElectricCar 类继承自 Vehicle,并且还实现了 IDrivable 和 IChargeable 接口,代表了一辆现代电动汽车。
using System;
// 可驾驶物品的接口public interface IDrivable{ void Start(); void Stop(); void Drive();}
// 可充电物品的接口public interface IChargeable{ int BatteryLevel { get; } void Charge();}
// 基类public abstract class Vehicle{ public string Make { get; set; } public string Model { get; set; }}
// 实现多个接口的派生类public class ElectricCar : Vehicle, IDrivable, IChargeable{ public int BatteryLevel { get; private set; } = 50;
public void Start() { Console.WriteLine("Electric car started silently."); }
public void Stop() { Console.WriteLine("Regenerative braking engaged. Car stopped."); }
public void Drive() { if (BatteryLevel > 0) { Console.WriteLine("Driving the car, whirring sound."); BatteryLevel -= 5; } else { Console.WriteLine("Battery is empty!"); } }
public void Charge() { Console.WriteLine("Charging..."); BatteryLevel = 100; Console.WriteLine("Battery fully charged."); }}
public class Program{ public static void Main() { var myTesla = new ElectricCar { Make = "Tesla", Model = "Model Y" }; myTesla.Start(); myTesla.Drive(); Console.WriteLine($"Current battery: {myTesla.BatteryLevel}%"); myTesla.Charge(); Console.WriteLine($"Current battery: {myTesla.BatteryLevel}%"); myTesla.Stop(); }}当上述代码被编译并执行时,会产生类似于以下的结果:
Electric car started silently.Driving the car, whirring sound.Current battery: 45%Charging...Battery fully charged.Current battery: 100%Regenerative braking engaged. Car stopped.