在面向对象编程(OOP)中,继承是代码复用和构建类层次结构的核心。通过继承,子类可以扩展或定制基类的功能,而无须从零开始构建。本文将带你深入探索 C# 继承的高级特性,掌握多态、虚函数、类型转换及构造器执行顺序等关键技术点。
1. 多态(Polymorphism)与引用转换
多态是继承的灵魂。在 C# 中,引用是多态的,这意味着父类型的变量可以指向其子类的对象。
引用转换
- 向上转换(Upcasting):从子类引用创建基类引用。这是隐式的,且仅影响引用类型,不影响被引用的对象本身。
- 向下转换(Downcasting) :从基类引用创建子类引用。这必须是显式的,因为它在运行时可能失败并抛出异常。
类型转换工具
为了安全地进行向下转换,C# 提供了以下运算符:
as运算符 :转换失败时返回null而非抛出异常。is运算符:检查对象是否满足特定模式(如是否属于某个特定类),常用于转换前的类型检查。- 模式变量 :C# 支持在
is检查的同时引入变量(如if (obj is Subclass s)),引入的变量可立即使用。
2. 虚函数成员与重写
子类可以通过重写基类的成员来改变其行为。
virtual关键字:允许在子类中被重写。方法、属性、索引器和事件均可声明为虚成员。override关键字:用于提供虚成员的特定实现。重写时,方法的签名、返回值和访问权限必须保持一致。- 协变返回类型(C# 9):允许重写方法时返回比基类定义更具体的派生类型。
⚠️ 安全警告:从构造器调用虚方法具有潜在危险。因为子类重写的方法可能会在子类字段尚未完全初始化之前被调用。
3. 抽象类与密封类
- 抽象类 (
abstract) :不能被实例化,仅作为基类使用。它可以包含不提供默认实现的抽象成员,强制子类实现这些成员。 - 密封类 (
sealed) :防止类被继承。同样,sealed也可以作用于重写的函数成员,防止其在更深层的子类中被再次重写。
4. 隐藏继承成员(Shadowing)
当子类定义了与基类同名的成员时,会发生成员隐藏。
- 编译器默认会发出警告。
- 使用
new修饰符 可以明确告诉编译器这种隐藏是有意为之,从而消除警告。 - 注意:
new修饰符隐藏成员与override重写成员在多态下的表现完全不同。
5. base 关键字的妙用
base 关键字在子类中有两个核心用途:
- 访问基类成员:调用被子类重写或隐藏的基类函数实现。
- 调用基类构造器:在子类构造器中显式指定调用父类的哪个构造方法。
6. 构造器的执行与初始化顺序
继承体系下的对象实例化有着严格的执行顺序,理解这一点对排查 Bug 至关重要:
总体原则
- 基类构造器优先:基类的初始化总是先于子类的特定初始化执行。
- 隐式调用 :如果子类构造器未显式使用
base关键字,编译器会自动尝试调用基类的无参数构造器。
详细步骤
| 阶段 | 执行动作 | 顺序 |
|---|---|---|
| 初始化阶段 | 字段初始化 & 计算基类构造器参数 | 从子类到基类 |
| 执行阶段 | 构造器方法体执行 | 从基类到子类 |
7. 方法重载与解析
当重载方法在继承体系中被调用时,编译器会根据以下规则决定执行哪个版本:
- 优先匹配最明确的类型:选择参数类型最贴近传入实参的重载版本。
- 静态决定 :具体调用哪个重载是在编译时静态决定的,而不是在运行时动态决定的。
技术总结 :
继承不仅是"获取父类的代码",更是一种"是一个(is-a)"的逻辑关系。通过合理使用 virtual、abstract 和 sealed,可以构建出既灵活又安全的类层次结构。在处理构造器时,务必注意字段初始化与方法体执行的先后逻辑。