接口定义“能做什么”的能力契约,抽象类定义“是什么”的类型契约;接口支持多实现、无字段,抽象类支持单继承、可含字段和构造函数。

接口和抽象类都能定义契约,但用法和语义完全不同。选错会导致设计僵硬、难以扩展,甚至引发重复代码。
核心区别:职责与能力
接口描述“能做什么”(能力契约),比如 IComparable 表示“能比较大小”,IDisposable 表示“能释放资源”。它不关心怎么实现,只规定必须提供哪些方法/属性/事件。
抽象类描述“是什么”(类型契约),代表一类事物的共性骨架。比如 Animal 抽象类可包含字段(Age)、已实现方法(Breathe())、抽象方法(MakeSound())——子类天然继承“是动物”这个身份。
语法限制决定适用场景
- 接口不能有字段、构造函数、析构函数,不能有访问修饰符(默认 public),所有成员隐式 abstract;C# 8.0+ 虽支持默认实现,但仍是“可选重写”,不改变其契约本质
- 抽象类可以有字段、构造函数、虚方法、密封方法、静态成员;子类必须通过
: base(...)调用父类构造器,体现“is-a”关系 - 一个类只能继承一个抽象类,但可实现多个接口——这是选择的关键线索:需要多继承能力?选接口
实际选型判断流程
- 想让不同类(如
Button、File、NetworkStream)都支持“释放资源”?→ 用IDisposable接口,无关类型层级 - 有一组紧密相关的类(
Dog、Cat、Bird),共享状态(Name)、基础行为(Eat())和待实现行为(Move())?→ 用Animal抽象类 - 已有类层次(如
Shape→Circle),现在要统一支持序列化?→ 不改继承链,添加ISerializable接口更安全 - 未来可能新增实现方式(比如从数据库查用户,将来还要从 API 查)?优先定义
IUserRepository接口,便于 Mock 和替换
常见误用及修正
错误:为单个类设计抽象基类,仅为了“看起来像面向对象”——没复用、没多态必要,纯属过度设计。
错误:在接口里塞大量默认实现,把接口当抽象类用——破坏接口轻量、组合灵活的初衷。
正确做法:先问“这些类是否本质同类?”再问“它们是否需要被不同维度归类?”前者倾向抽象类,后者倾向接口。混合使用很常见:比如 Stream 是抽象类(定义字节流基本结构),同时实现 IDisposable 和 IAsyncDisposable(叠加能力契约)。
基本上就这些。不复杂但容易忽略——关键不在语法,而在建模时想清楚:你在定义“角色”,还是在定义“身份”。








