C# 学习笔记 08:签订契约!抽象类(Abstract)与接口(Interface)
开篇:不存在的「怪物」
皮皮最近在试玩自己的游戏, 突然发现了一个大 Bug。
他写了这样一行代码:
Monster m = new Monster();
然后游戏里出现了一个隐形人。
它有血量, 能被打, 但没有模型、没有贴图、没有形态。
因为——“怪物”只是一个统称。
前辈敲了敲黑板:
“你可以说‘史莱姆是怪物’, 但你不能说‘我养了一只概念’。概念,不应该被 new 出来。”
于是今天的问题就很明确了:
如何禁止制造“概念”? 又如何规定:只要你是某种东西,就必须具备某种能力?
第一关:未完成的蓝图(Abstract Class)
如果一个类只配当父类, 不配被实例化, 那就给它加一个词:
abstract
// 🚫 这是一个“不完整”的类publicabstractclassMonster{publicstring Name;publicint Hp;// 📝 只有声明,没有实现// 意思是:是怪物,就必须会攻击publicabstractvoidAttack();}
这一刻,Monster 的身份发生了变化:
- ✅
new Dragon() → 只要你把 Attack 写完整
抽象类 = 半成品蓝图
它规定了长什么样, 也规定了必须有哪些行为, 但拒绝被直接使用。
瓜瓜一句话总结
抽象类就像一张填空题试卷: 题目我出, 答案你必须写。

第二关:跨物种的通用能力(Interface)
皮皮又遇到了新需求。
他希望:
问题是:
怎么办?
答案是:接口(Interface)
接口解决的不是「你是谁」, 而是:
“你具备什么能力”
定义一份契约
// 📜 能挨打的资格证publicinterfaceIDamageable{voidTakeDamage(int amount);}
接口只做一件事:
声明能力,不提供实现
谁都可以签
// 怪物签了publicclassDragon : Monster, IDamageable{publicoverridevoidAttack() { Console.WriteLine("巨龙喷火!"); }publicvoidTakeDamage(int amount) { Hp -= amount; Console.WriteLine("龙鳞挡住了一部分伤害!"); }}
// 箱子也能签(它跟 Monster 没任何血缘关系)publicclassWoodenBox : IDamageable{publicint Durability = 50;publicvoidTakeDamage(int amount) { Durability -= amount; Console.WriteLine("箱子碎成了木屑!"); }}
只要实现了接口, 系统就可以用同一种方式对待它们。
这就是接口真正的威力。
第三关:抽象类 vs 接口(必考对比)
这是面试官最爱的问题, 也是设计时最容易纠结的地方。
用一句话区分:
抽象类:你是谁 接口:你能干什么
怎么选?
- 对象之间毫不相关 → 接口几乎是唯一选择

第四关:多重能力的勇者
C# 不允许你有两个爹, 但允许你考无数证。
publicclassSuperPipi : Penguin, IFlyable, IAttackable, IDamageable{// 所有能力,自己实现}
这才是现代 C# 里真正灵活的设计方式:
本期总结
你今天真正掌握的是:
- Abstract👉 不能
new 的父类模板,用来约束结构和行为 - 设计核心👉 抽象类解决「是什么」 👉 接口解决「能做什么」
课后思考
现代 C# 里, 接口已经可以写默认实现了, 看起来越来越像抽象类。
但设计层面仍然坚持一个原则:
为什么 C# 不允许多重继承?
提示一句就够了:
如果两个父类都有 Move(), 儿子听谁的?
下期预告
面向对象的三座大山 (封装、继承、多态/抽象) 我们都翻过去了!
下一期,我们要稍微休息一下,进入“番外篇”,解决之前遗留的那个“魔法背包”问题——详解 Ref, Out 和 Tuples,看看如何让方法一次带回好几件装备!