c# 继承和多态详解

来源:这里教程网 时间:2026-02-21 17:39:22 作者:

继承和多态不是两个独立概念,而是一体两面:继承是结构基础,多态是行为表现——没继承,多态无从谈起;没抽象或虚成员,多态就只是普通调用。

什么时候必须用
abstract

当你在父类中「只定义行为契约,不提供实现」时,就必须用

abstract
。比如所有动物都要
MakeSound()
,但狗叫、猫叫、鸟鸣各不相同,父类无法写死逻辑。

抽象方法必须放在
abstract class
中,且子类非抽象时,
override
是强制的
抽象类可以没有抽象方法(比如只做类型约束或共享字段) 不能
new
抽象类实例,哪怕它有构造函数
错误现象:
'Animal' does not implement inherited abstract member 'Animal.MakeSound()'
—— 子类忘了加
override
或漏了方法体

什么时候该用
virtual
而不是
abstract

当父类能提供一个「合理默认行为」,但允许子类按需覆盖时,用

virtual
。比如
ToString()
默认返回类型名,子类可重写为返回更友好的格式。

虚方法可以被子类
override
,也可以不重写,直接沿用父类逻辑
子类中若想调用父类原版实现,得显式写
base.MethodName()
常见误用:把本该是
virtual
的方法写成
private
sealed
,导致子类无法扩展
性能影响极小,但过度虚化(比如每个 getter 都
virtual
)会轻微增加虚表查找开销,一般无需担心

多态真正生效的两个典型场景

多态不是“写了

override
就自动多态”,它只在「通过父类引用调用虚/抽象方法」时才触发。关键看变量声明类型,不是实际对象类型。

Animal a1 = new Dog();
Animal a2 = new Cat();
Console.WriteLine(a1.MakeSound()); // 输出 "Woof!"
Console.WriteLine(a2.MakeSound()); // 输出 "Meow!"
参数多态:
void Feed(Animal animal) { animal.Eat(); }
—— 传
Dog
就执行
Dog.Eat()
,传
Bird
就执行
Bird.Eat()
返回值多态:
Animal Create(string type) => type switch { "dog" => new Dog(), "cat" => new Cat() };
—— 调用方只需按
Animal
处理,不用关心具体类型
容易踩的坑:用子类变量直接调用,比如
Dog d = new Dog(); d.MakeSound();
—— 这走的是静态绑定,不触发多态,哪怕方法是
virtual
abstract
注意:只有
virtual
abstract
override
成员参与多态;
private
static
sealed override
均不参与

protected
base
在继承链中的真实作用

protected
不是“给子类用的 public”,而是「仅限派生类内部访问」的访问修饰符;
base
是子类访问父类成员的唯一安全通道。

protected
成员可在子类中直接使用(如
this.Name
),但不能通过子类实例访问(
new Dog().Name
报错)
子类构造函数必须显式或隐式调用基类构造函数;若基类无无参构造,子类必须用
: base(...)
指定
常见错误:
base
调用位置不对(必须是构造函数第一行)、或在静态方法里误用
base
不要用
protected
暴露内部状态,优先考虑
protected virtual
方法供子类定制行为,而非暴露字段

最常被忽略的一点:多态依赖运行时类型信息(RTTI),而 .NET 的 JIT 编译器对虚调用做了高度优化,所以别因“怕慢”而回避

virtual
—— 真正的性能瓶颈几乎从来不在这里,而在设计失当导致的深层继承链或过度抽象。

相关推荐