接口和抽象类应按问题类型选择:接口定义行为契约,适合多能力组合;抽象类提供基线实现与状态共享,适合复用初始化逻辑。误用会导致继承混乱、重复代码或测试困难。

接口和抽象类不是“选哪个更好”,而是“解决哪类问题”。用错场景会导致后期改起来费劲、继承链混乱、或者被迫写大量重复代码。
接口不能有字段,但抽象类可以存状态
接口里声明 int Age 会编译失败(C# 10 起允许 static 字段,但仅限常量语义);而抽象类能定义实例字段、protected 成员、构造函数——适合封装共享状态或初始化逻辑。
- 需要记录创建时间、缓存计算结果、维护内部计数器 → 用抽象类
- 只约定“谁有
Start()方法”,不关心它内部怎么记日志或更新状态 → 接口更干净 - 字段误塞进接口后,发现所有实现类都得手动复制一遍初始化逻辑,这就是典型踩坑点
一个类只能继承一个抽象类,但能实现多个接口
这是最硬的语法限制:class Car : Vehicle, ILoggable, IExportable 合法;class Car : Vehicle, AnotherBase 直接报错 CS0261。
- 多能力组合(比如“可序列化 + 可验证 + 可审计”)→ 接口是唯一选择
- 想复用某套初始化流程 + 默认行为(比如网络请求基类统一处理 token、重试、超时)→ 抽象类不可替代
- 试图用抽象类模拟多继承,最后发现子类要反复拷贝父类逻辑,反而更难维护
C# 8+ 接口支持默认实现,但不能替代抽象类
interface ILogger 可以写 void Log(string msg) => Console.WriteLine(msg),但它仍不能有字段、不能调私有方法、所有成员默认 public。
- 默认实现只是“避免强制重写”,不是“鼓励放业务逻辑”
- 如果默认方法里要读配置、查数据库、调用其他服务 → 放抽象类里更可控(能加
protected virtual、能注入依赖) - 把复杂逻辑塞进接口默认方法,后续想 mock 测试会非常别扭(接口本应轻量)
抽象方法必须被 override,接口方法直接实现即可
抽象类里的 public abstract void Run(); 在子类中必须写 public override void Run();而接口的 void Run(); 子类直接写 public void Run() 就行。
- 抽象类强调“我定义了这个行为,你得按我的方式重写”
- 接口强调“你只要提供这个行为,名字对、签名对就行”
- 新手常在这里混淆:给接口方法加
override关键字,编译器会报错 CS0115(“没有可重写的成员”)
真正容易被忽略的是设计意图的混用:比如用接口去传参却忘了它无法约束构造过程,导致依赖注入时拿不到已初始化的对象;或者在抽象类里塞了一堆 public 方法,结果子类暴露了不该暴露的内部逻辑。区分清楚“契约”和“基线实现”,比记住语法细节更重要。











