多态在C#中是绕不开的机制,只要使用继承、接口或虚方法即已应用;必须用virtual和override实现运行时多态,如Shape基类的Draw方法被Circle、Rectangle等重写,通过List统一调用各子类特有逻辑。

多态在 C# 里不是“用不用”的问题,而是“怎么绕不开”的问题——只要用了继承、接口或虚方法,你就已经在用多态了。关键在于是否意识到它在起作用,以及是否在设计时主动利用它来降低耦合、提升可扩展性。
什么时候必须用 virtual 和 override?
当你有一组行为相似但实现不同的类(比如 Circle、Rectangle、Triangle),又希望用统一方式调用它们各自特有的逻辑时,就必须让基类方法可被重写。
常见错误现象:Draw() 方法在子类里写了新逻辑,但调用时始终执行基类版本——根本原因是没加 virtual(基类)和 override(子类)。
-
virtual必须加在基类方法声明上,否则子类无法合法使用override - 子类中用
override而不是new,否则只是隐藏而非多态重写 - 如果基类方法逻辑完全不通用,直接定义为
abstract更合适(强制子类实现)
为什么 List<shape></shape> 能装不同子类对象?
这是运行时多态最直观的体现:CLR 允许派生类对象以基类类型“身份”存入集合,但调用方法时仍按真实类型执行。
使用场景:绘图系统、报表导出器、支付渠道适配器等需要统一调度多种具体实现的地方。
- 声明类型是
Shape,但实际对象可能是Circle实例,typeof(obj)返回的是Circle - 遍历时调用
obj.Draw(),CLR 自动查虚方法表,跳转到Circle.Draw() - 不能反过来:不能把
List<circle></circle>直接赋给List<shape></shape>变量(泛型协变需out修饰,且仅适用于接口如IEnumerable<out t></out>)
接口多态比继承多态更灵活吗?
是的,但不是“更高级”,而是适用面不同。接口多态解决的是“能做什么”,继承多态解决的是“是什么”。
参数差异:
- 一个类可以实现多个接口(
IDrawable、ISerializable、IComparable),但只能继承一个基类 - 接口不能有字段或构造函数,也不能提供默认实现(C# 8+ 支持默认接口方法,但仍是契约优先)
- 用接口做依赖注入(如
IPaymentService)时,替换实现无需改调用方代码;而继承体系一旦定型,修改基类影响范围更大
性能影响几乎可忽略,但编译器对接口调用的内联优化不如虚方法直接,不过实际业务代码中这点差异无感。
容易被忽略的细节:构造函数里调用虚方法很危险
在基类构造函数中调用 virtual 方法,子类重写版本可能被执行,但此时子类字段还未初始化——这是典型的“部分构造对象”陷阱。
示例:Shape 构造函数里调用了 this.Draw(),而 Circle 的 Radius 字段还在默认值 0,Draw() 却试图用它算面积。
- 永远避免在构造函数中调用虚成员
- 如果必须触发子类逻辑,改用模板方法模式:基类构造函数调用
protected abstract void OnInitialized() - 或者延迟到
Initialize()这类显式初始化方法中再调用
这种问题不会报错,但结果不可预测,调试起来特别费时间。


















