目录
一、C#中的接口
C# 中,可以使用 interface
关键字定义接口。接口实际上是一种抽象集。我之所以使用“抽象集”这个词描述接口,是因为接口的作用就是约定一些抽象的性质和行为。
假设我正在写一本小说,我在为小说人物写设定。我平常喜欢看特摄作品,诸如《假面骑士空我》、《假面骑士亚极陀》、《假面骑士创骑》和《假面骑士Ex-aid》等,假面骑士中有一个设定——敌我同源1
。我想把敌我同源的设定用在小说人物里,在此之前我先剖析一下《假面骑士Ex-aid》的设定。
在这部作品当中,敌人是 游戏病病毒 ,镜飞彩(天才外科医生)、九条贵利矢(法医) 和
檀黎斗(创梦社长) 都能够变身成假面骑士是因为他们接受了适能者手术。游戏病病毒的力量来自创梦集团的游戏卡带,假面骑士们的力量也来自游戏卡带——力量来源是游戏卡带。
在《假面骑士创骑》中,敌人是吸收了星云气体而变成怪人的人类,假面骑士是吸收了星云气体仍能保持理智的人类,并使用满装瓶变身,与怪人作战。——力量来源是星云气体。
我介绍了这两部特摄作品中的设定,怪人和假面骑士的力量来源均来自同一事物。如果我想把敌我同源的设定运用在我的小说中,应该如何做呢?我会这样做:
- 确定力量来源的设定,比如这股力量的来源是一块石头,石头有形状、大小和颜色,可以释放力量包裹全身。
- 接下来确定主角的力量设定,比如主角使用力量和怪人使用力量是不同的方式,主角需要把石头放在特制的抑制容器中才能变身,以防止暴走。主角的石头的形状均为三角形,颜色为绿色。主角能够利用力量发射光线。
- 再确定怪人的力量设定,比如怪人需要吞下石头才能获得力量,而且每次都会暴走。怪人的石头的形状为圆形,颜色为红色。不同的怪人能够利用力量使出不同的技能。
- 在写的过程中新增各种各样的怪人力量设定,比如量产版怪人,吞下的石头都是灰色的,使用的技能都是相同的。
我第一步做的事情是确定力量来源的设定,其实我就是提前约定好力量的本质,但是我不具象化它,因为不同的人的力量来源的性质和使用力量的方式也不同,所以我先抽象化出一些相同的性质,我上面举的例子中提到的石头是力量来源,性质有形状、大小和颜色,行为是使用石头时会释放出力量包裹全身。对于主角和怪人,我是后面再逐渐具象化并拓展的。
接口也是同理,接口是提前约定好一些成员,但是只是抽象化,暂时不实现(具象化)。后面使用类继承该接口再实现其中的成员。
似乎无关的东西写的多了,但是毕竟在我脑子里浮现出来了,写下来也顺理成章……
二、C#接口语法介绍
接下来正式介绍接口的语法,包括定义接口、实现接口等语法,请读者看完一部分内容就及时动手体会一下。
点击这里可以在官方文档中查看接口的介绍Interfaces - define behavior for multiple types - C# | Microsoft Learn
定义
C#中定义接口使用 interface 关键字,interface <接口名> 。接口名前应该加一个 I
来标识这是个接口。接口中可以声明属性、方法、索引器和事件,但是不能声明字段、构造函数和析构函数。从继承这个概念角度出发,interface
实际上解决了 class 不能同时继承多个 class 的问题。例如,我定义了三个类 class Person,
class Father, class Mother。Person 不能同时继承 Father 和
Mother,只能从中选一个继承。但是 class 可以继承多个 interface 。定义了两个接口
interface IFather, interface IMother,Person 可以同时继承这两个接口
IFather, IMother。
interface IPowerSource
{
string shape; //编译器会报错
int size; //编译器会报错
string color; //编译器会报错
}
声明成员字段时,Visual Studio 中编译器报错“接口不能包含实例字段“,
interface IPowerSource
{
string Shape{get;set;}//自动属性的写法
int Size{get;set;}
string Color{get;set;}
}
接口中可以声明属性,其中 Shape , Size , Color 为成员属性。分别表示
形状,尺寸,颜色。
interface IPowerSource
{
public string Shape{get;set;}
public int Size{get;set;}
public string Color{get;set;}
protected void ReleasePower();
public void PrintName();
}
接口的成员默认访问类型是 public ,可以修改,如 ReleasePower() 修改为了 protected,当然也可以修改为
internal。但是,唯独不能修改为private,原因很简单——接口需要被实现,如果某成员访问类型是
private ,那接口里定义的该成员不能被实现,也就没有意义。
接口的成员方法不能有方法体。
实现
隐式实现
类继承接口时需要实现接口中声明的成员。类继承接口应这样写 class <类名> : <接口名> 。如果继承多个接口时,应用逗号分隔开接口,
class <类名> : <接口名> , <接口名> , ..., <接口名>
继承接口的类需要实现该接口的所有成员。
class Person : IPowerSource
{
public string Shape { get; set; } = "三角形"; //为属性赋默认值
public int Size { get; set; } = 10;
public string Color { get; set; } = "绿色";
public void ReleasePower()
{
}
public void Print()
{
Console.WriteLine($"形状:{Shape};尺寸:{Size};颜色:{Color}");
}
}
Person 类隐式实现了接口 IPowerSource 。
internal class Program
{
static void Main(string[] args)
{
Person person = new Person(); //实例化
IPowerSource person2 = new Person(); //根据里氏替换原则,任何父类出现的地方都能使用子类替代
person.Print(); //输出:“形状:三角形;尺寸:10;颜色:绿色”
person2.Print(); ////输出:“形状:三角形;尺寸:10;颜色:绿色”
}
}
实例化了两个对象,并调用它们的Print()方法。根据里氏替换原则,任何父类出现的地方都能使用子类替代,描述的是子类可以在父类的基础上“做加法”,但是不能“做减法”。
显示实现
显示实现时,需要在成员前加上接口名称和访问修饰符 '.' 。
什么时候会想显示实现?——当你有两个接口,两个接口中有同名方法时,实现的同名方法内容不一样,例如:
public interface IControl
{
void Paint();
}
public interface ISurface
{
void Paint();
}
public class SampleClass : IControl, ISurface
{
//隐式实现
public void Paint()
{
Console.WriteLine("Paint method in SampleClass");
}
}
internal class Program
{
static void Main(string[] args)
{
SampleClass t1 = new SampleClass();
IControl t2 = new SampleClass();
ISurface t3 = new SampleClass();
t1.Paint(); //输出"Paint method in SampleClass"
t2.Paint(); //输出"Paint method in SampleClass"
t3.Paint(); //输出"Paint method in SampleClass"
}
}
public class SampleClass : IControl, ISurface
{
//显示实现
void IControl.Paint()
{
System.Console.WriteLine("IControl.Paint");
}
//显示实现
void ISurface.Paint()
{
System.Console.WriteLine("ISurface.Paint");
}
}
internal class Program
{
static void Main(string[] args)
{
SampleClass t1 = new SampleClass();
IControl t2 = new SampleClass();
ISurface t3 = new SampleClass();
t1.Paint(); //编译错误,因为SampleClass中没有定义Paint()方法
t2.Paint(); //输出"IControl.Paint"
t3.Paint(); //输出"ISurface.Paint"
}
}
可见,如果显示实现,那么只有使用接口类型的对象才能使用各自的 Paint() 方法。
IControl 类型的 t2 对象才能使用 IControl.Paint();ISurface 类型的
t3 对象才能使用 ISurface.Paint()。
如果我想让 SampleClass 类型的 t1 对象也能够使用 Paint() 方法,那么需要在
SampleClass 中定义:
public void Paint()
{
Console.WriteLine("SampleClass.Paint");
}
总结
接口是用来约定一些抽象的性质和行为的。
使用 interface 关键字声明接口。
类只能有一个父类,但是能继承多个接口。
接口的成员可以是属性、事件、索引器和方法,不能是字段、构造函数和析构函数。
接口的成员不能是 private 的。
接口的成员方法不能有方法体。
根据里氏替换原则,任何父类出现的地方都可以使用子类来替代。任何接口出现的地方都可以使用继承该接口的类来替代。
接口的隐式实现和显式实现区别在于调用者是谁。
Footnotes
-
敌我同源,指的是假面骑士实际上和怪人的力量是同一性质的,只是假面骑士希望用力量守护人们,所以被称为假面骑士。 ↩