CS3L20——接口

本章代码关键字

1
interface    //接口关键字

接口

接口是行为的抽象规范,它也是一种自定义类型,关键字:inferface

继承接口和继承类的区别

  • 继承类 是对象间的继承 包括特征行为等
  • 继承接口 是行为间的继承 继承接口的行为规范 按照规范去实现内容

由于接口也是遵循 里氏替换原则,所以可以用接口容器装对象,
那么就可以实现装载 各种毫无关系但是却有相同行为 的对象

  1. 接口内部只能包含成员方法、索引器、属性、事件,且都不实现,都没有访问修饰符,
  2. 类可以继承多个接口,但只能继承一个类
  3. 接口可以继承接口 相当于在进行行为合并 带子类继承时再去实现具体的行为
  4. 接口可以被显式实现 主要用于实现不同接口中的同名函数的不同表现
  5. 实现接口的方法 可以加 virtual​ 关键字 在之后的子类里重写

需要执行某种行为,就以行为对应的接口类型作为参数,传入拥有这种行为的对象(继承了对应接口的类对象)
这样,执行行为就不需要指定具体的类型,以摆脱对具体实现的依赖

因此实际开发中,建议多多使用接口抽象行为,通过接口类型变量接收具体类对象,通过接口调用方法

接口只规定一个类有什么行为,而不关心行为如何实现,因此相比继承基类,接口更灵活也更加解耦合(不会耦合基类的实现)

例如,飞 这种行为可以单独抽象成接口,
这样 继承动物类的鸟类 和 继承机器类的飞机类 都可以继承该接口表明这两个类拥有这种行为,
这样就不需要把 飞 耦合到它们各自的基类内,毕竟不是所有的动物和机器都会飞

接口申明的规范:

  • 不包含成员变量,只包含:方法、属性、索引器、事件,且成员不能被实现
  • 成员可以不用写访问修饰符,不能是私有的
  • 接口不能继承类,但可以继承另一个接口

接口的使用规范:

  • 类可以继承多个接口
  • 类继承接口后,必须实现接口中的所有成员

特点:

  • 它与类的申明相似
  • 接口是用来继承的
  • 接口不能被实例化,但可以作为容器存储对象

C# 接口的更多特性可见:一期视频看透C#接口的全部特性及用法_哔哩哔哩_bilibili(注:Unity开发不能全部使用其中的介绍的特性)

关于接口和抽象类的区别,详见:CS3L27——抽象类和接口的区别

接口的声明

接口关键字:interface​,接口可以认为是抽象行为的基类,接口命名规范 帕斯卡前面加 I
不包含成员变量,只包含方法、属性、索引器、事件,成员不能被实现

1
2
3
interface 接口名
{
}

声明示例:

1
2
3
4
5
6
7
8
interface IFly
{
//接口不能声明成员变量,成员也不能是私有的
void Fly(); //注意 接口里的方法 必须要在 继承接口的子类 里实现,因此不能用private 不写访问修饰符默认是public
string Name { set; get; } //接口内的成员属性只能像自动属性一样写,不能在其中具体写实现,具体实现需要留到继承接口后的子类里去写
int this[int index] { get; set; } //接口里的索引器也类似于接口里的属性,不能在其中具体写实现,需要留到继承接口后的子类里去写
event Action doSomething; //接口可以实现事件,事件具体是什么会在进阶讲解
}

接口的使用

接口用来继承,它表明某个类拥有这个接口的行为,接口也遵循里氏替换原则
类可以继承一个类,n个接口,继承了接口后,必须实现其中的内容,且必须是 public​ 的
实现的接口函数,可以加 virtual​ 再在子类重写

Visual Studio 可以点击灯泡图标里的实现接口来自动实现接口内的所有内容

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Animal { }

class Person : Animal, IFly
{
//实现的接口函数,可以加virtual再在子类重写
public virtual void Fly() { }

public string Name
{
get => Name;
set => Name = value;
}
public int this[int index]
{
get => 0;
set => this[index] = value;
}

public event Action doSomething;
}

internal class Program
{
static void Main(string[] args)
{
//IFly f = new IFly(); //接口不能被实现
IFly f = new Person(); //接口遵循里氏替换原则
}
}

接口可以继承接口

接口继承接口时,不需要实现,待类继承接口后 类自己去实现所有内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
interface IWalk
{
void Walk();
}

interface IMove:IFly, IWalk
{
void Move();
}

class Test : IMove
{
public int this[int index] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }

public string Name { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }

public event Action doSomething;

public void Fly()
{
throw new NotImplementedException();
}

public void Move()
{
throw new NotImplementedException();
}

public void Walk()
{
throw new NotImplementedException();
}
}

internal class Program
{
static void Main(string[] args)
{
//类继承的所有接口,包括接口继承的接口都遵循里氏替换原则
IMove im = new Test();
IFly If = new Test();
IWalk iw = new Test();

Player p = new Player();
ISuperAtk isa = new Player();
IAtk ia = new Player();
}
}

显式实现接口

当一个类继承两个接口,但是接口中存在着同名方法时,直接只实现一次同名方法是可以运行的,但是这样无法区分两个接口的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface IAtk
{
void Atk();
}

interface ISuperAtk
{
void Atk();
}

//可以用灯泡图标里的显式实现所有成员来快速显式实现接口
class Player : IAtk, ISuperAtk
{
public void Atk() { } // 如果不显式实现接口,虽然可以运行,但是这样就无法区分SuperAtk和Atk的区别了,继承两个接口的意义也不明
}

internal class Program
{
static void Main(string[] args)
{
Player p = new Player();
p.Atk()
}
}

如果需要区分两个接口的行为,为不同的接口分别实现不同的方法,则需要显式实现接口
显式实现接口 就是用 接口名.行为名​ 去实现

注意:显式实现接口时 不能写访问修饰符,如果接口里有方法是用 protected​ 修饰的也要用显式实现接口

显式实现接口后,接口的显式实现方法不能被直接点出来调用了,改成父类装子类或者用 as​ 来使用

指的一提的是,即使显式实现了多个接口的同名方法,
还是可以再实现一个没有显式实现的方法,通过类对象本身的类型执行
但是要注意,这个方法和其他显式实现方法不构成重载! 因为它们本质上不是一个名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
interface IAtk
{
void Atk();
}

interface ISuperAtk
{
void Atk();
}

class Player : IAtk, ISuperAtk
{
//显式实现接口 就是用接口名.行为名 去实现
void IAtk.Atk()
{
Console.WriteLine("显式实现IAtk的方法");
}

void ISuperAtk.Atk()
{
Console.WriteLine("显式实现ISuperAtk的方法");
}
//显式实现接口后 再在类里写一个atk方法也可以 注意:这不是重载!
public void Atk()
{
Console.WriteLine("不是显式实现的方法");
}
}


internal class Program
{
static void Main(string[] args)
{
Player p = new Player();
ISuperAtk isa = new Player();
IAtk ia = new Player();

//显式实现接口后,接口的方法不能被直接点出来调用了,改成父类装子类或者用as来使用
p.Atk();
(p as IAtk).Atk();
(p as ISuperAtk).Atk();
isa.Atk();
ia.Atk();
}
}

输出:

1
2
3
4
5
不是显式实现的方法
显式实现IAtk的方法
显式实现ISuperAtk的方法
显式实现ISuperAtk的方法
显式实现IAtk的方法