Files
My-Notes/笔记/CSharp/核心/接口.md
T
2026-06-21 09:51:55 +08:00

477 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 接口
概念:
接口使用 `interface` 关键字定义
它主要用来规定一组==能力 / 规则 / 契约==
简单理解:
接口不关心“你是谁”
只关心“你能不能做这件事”
例如:
`IFlyable` 表示“会飞”
不管你是鸟、飞机还是飞行道具,只要实现了这个接口,就代表你拥有飞行能力
通常用法:
1. 规定某些类必须实现哪些成员
2. 表达一种能力,例如会飞、会攻击、可保存、可移动
3. 让不同类型用同一套调用方式
4. 弥补 C# 类只能单继承的问题,因为一个类可以实现多个接口
5. 配合[[多态vob|多态]],让代码面向能力编程
----
# 语法
定义接口:
```csharp
interface 接口名
{
void 方法名();
}
```
实现接口:
```csharp
class 类名 : 接口名
{
public void 方法名()
{
}
}
```
命名习惯:
接口名通常以大写字母 `I` 开头
例如:`IFlyable``IAttackable``ISaveable`
----
# 基础案例
```csharp
interface IFlyable
{
void Fly();
}
class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("鸟在飞");
}
}
class Plane : IFlyable
{
public void Fly()
{
Console.WriteLine("飞机在飞");
}
}
void LetItFly(IFlyable flyable)
{
flyable.Fly();
}
LetItFly(new Bird());
LetItFly(new Plane());
```
理解:
1. `IFlyable` 规定实现者必须有 `Fly()` 方法
2. `Bird``Plane` 类型不同,但都实现了 `IFlyable`
3. `LetItFly` 不关心传进来的是鸟还是飞机,只关心它会不会飞
这就是接口常见用法:
==面向能力编程,而不是面向具体类型编程==
----
# 接口中的成员
常见接口成员:
```csharp
interface IMovable
{
void Move();
int Speed { get; set; }
}
```
实现:
```csharp
class Player : IMovable
{
public int Speed { get; set; }
public void Move()
{
Console.WriteLine("玩家移动");
}
}
```
接口中常见成员包括:
1. 方法
2. 属性
3. 索引器
4. 事件
初学阶段重点先掌握:
接口方法 + 接口属性
----
# 接口成员默认是 public
接口里这样写:
```csharp
interface IAttackable
{
void Attack();
}
```
实现时必须写 `public`
```csharp
class Enemy : IAttackable
{
public void Attack()
{
Console.WriteLine("敌人攻击");
}
}
```
错误写法:
```csharp
class Enemy : IAttackable
{
void Attack()
{
}
}
```
原因:
接口成员代表对外公开的契约
类实现接口时,成员访问级别不能比接口更低
----
# 一个类可以实现多个接口
C# 的类只能继承一个父类,但可以实现多个接口。
```csharp
interface IMovable
{
void Move();
}
interface IAttackable
{
void Attack();
}
class Player : IMovable, IAttackable
{
public void Move()
{
Console.WriteLine("玩家移动");
}
public void Attack()
{
Console.WriteLine("玩家攻击");
}
}
```
理解:
`Player` 同时拥有“可移动”和“可攻击”两种能力
----
# 继承类和实现接口同时存在
写法:
```csharp
class 子类名 : 父类名, 接口1, 接口2
{
}
```
注意顺序:
==先写父类,再写接口==
案例:
```csharp
class Character
{
public string Name;
}
interface IAttackable
{
void Attack();
}
class Warrior : Character, IAttackable
{
public void Attack()
{
Console.WriteLine(Name + "挥剑攻击");
}
}
```
错误示意:
```csharp
// class Warrior : IAttackable, Character
// {
// }
```
如果既要继承类,又要实现接口,父类必须写在最前面。
----
# 接口也可以继承接口
接口之间可以继承,而且可以继承多个接口。
```csharp
interface IMovable
{
void Move();
}
interface IAttackable
{
void Attack();
}
interface IPlayerAction : IMovable, IAttackable
{
void UseItem();
}
```
实现 `IPlayerAction` 的类,必须实现:
1. `Move()`
2. `Attack()`
3. `UseItem()`
```csharp
class Player : IPlayerAction
{
public void Move()
{
}
public void Attack()
{
}
public void UseItem()
{
}
}
```
----
# 接口引用和多态
接口也可以当变量类型使用。
```csharp
IAttackable attacker = new Player();
attacker.Attack();
```
也可以作为方法参数:
```csharp
void DoAttack(IAttackable attacker)
{
attacker.Attack();
}
```
好处:
调用方只依赖 `IAttackable`
任何实现了这个接口的对象都能传进来
```csharp
DoAttack(new Player());
DoAttack(new Enemy());
```
这也是多态的一种表现。
----
# 接口和里氏替换原则
接口常用来拆分能力,避免不合理继承。
比如:
```csharp
abstract class Bird
{
public string Name;
}
interface IFlyable
{
void Fly();
}
class Sparrow : Bird, IFlyable
{
public void Fly()
{
Console.WriteLine("麻雀飞");
}
}
class Penguin : Bird
{
public void Swim()
{
Console.WriteLine("企鹅游泳");
}
}
```
这样:
1. `Bird` 只表达“鸟”
2. `IFlyable` 只表达“会飞”
3. 不会飞的企鹅不用硬实现 `Fly()`
这个例子可以和[[里氏替换原则]]一起理解。
----
# 显式实现接口
当两个接口有同名方法,或者你不希望方法直接暴露在类对象上,可以显式实现接口。
```csharp
interface IWalkable
{
void Move();
}
interface IFlyable
{
void Move();
}
class Bird : IWalkable, IFlyable
{
void IWalkable.Move()
{
Console.WriteLine("鸟在走");
}
void IFlyable.Move()
{
Console.WriteLine("鸟在飞");
}
}
Bird bird = new Bird();
IWalkable walkable = bird;
walkable.Move();
IFlyable flyable = bird;
flyable.Move();
```
注意:
显式实现接口时,不写 `public`
调用时通常要先把对象当成对应接口类型
初学阶段先知道有这个写法即可,最常用的还是普通实现接口。
----
# 常见错误
错误1:实现接口时忘记 `public`
```csharp
class Player : IAttackable
{
void Attack()
{
}
}
```
接口成员默认是公开契约,实现时通常要写 `public`
----
错误2:没有实现接口所有成员
```csharp
interface IMovable
{
void Move();
int Speed { get; set; }
}
class Player : IMovable
{
public void Move()
{
}
}
```
`Speed` 没实现,所以会报错。
----
错误3:把接口当成类直接创建对象
```csharp
IFlyable flyable = new IFlyable();
```
接口只是规则,不能直接 `new`
正确写法:
```csharp
IFlyable flyable = new Bird();
```
----
# 什么时候用接口
适合用接口的情况:
1. 你要表达的是“能力”,不是“父子身份”
2. 多个无关类型都有同一种行为
3. 一个类型需要拥有多种能力
4. 想让代码依赖规则,而不是依赖具体类
例如:
- `IFlyable`:会飞
- `IAttackable`:能攻击
- `ISaveable`:可保存
- `IDamageable`:可受伤
----
# 注意
1. 接口用 `interface` 定义
2. 接口名通常以 `I` 开头
3. 接口不能直接创建对象
4. 类和结构体都可以实现接口
5. 一个类可以实现多个接口
6. 接口成员实现时通常要写 `public`
7. 接口更适合表达“能做什么”,抽象类更适合表达“是什么”
8. 接口常和多态、里氏替换原则一起理解
----
# 一句话记忆
==接口是能力契约,谁实现它,谁就必须按规则提供这些能力。==
再压缩一点:
==接口管能不能做,不管你是谁。==
----
# 面试/复习时怎么说
可以这样答:
“接口是用 `interface` 定义的一组契约,主要用来规定实现类必须提供哪些成员。
类可以实现多个接口,所以接口常用来表达能力,比如可移动、可攻击、可保存。
调用方可以面向接口编程,只依赖能力规则,不依赖具体类,这样更利于扩展和多态。”
----
引用:
1. Microsoft Learn: [interface (C# Reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface)
2. Microsoft Learn: [Interfaces](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/interfaces)
#接口
#核心