C#接口用法详解
由于 C# 中的类不支持多重继承,但是现实世界出现多重继承的情况又比较多。因此,为了避免传统的多重继承给程序带来的复杂性等问题,同时保证多重继承带给程序员的诸多好处,C# 中提出了接口的概念。
在 C# 中,使用接口可以实现多重继承的功能。
例如,在组装计算机时,主板与机箱之间就存在一种事先约定,不管什么型号或品牌的机箱、什么种类或品牌的主板,都必须遵守一定的标准来设计制造。因此在组装计算机时,计算机的零配件都可以安装在现今的大多数机箱上。接口就可以看作这种标准,它强制性地要求子类必须实现接口约定的规范,以保证子类必须拥有某些特性。
在 C# 中声明接口时需要使用 interface 关键字,其语法格式如下:
接口具有以下特征:
例如,使用 interface 关键字定义一个 Information 接口,在该接口中声明 Code 和 Name 两个属性,分别表示编号和名称;然后声明一个方法 ShowInfo,用来输出信息。代码如下:
通过继承接口实现输出进货信息和销售信息的功能,代码如下:
例如,在 JHInfo 类中实现 Code 和 Name 属性的代码可以修改成如下形式:
运行结果为:
上面的实例中只继承了一个接口,接口还可以多重继承。使用多重继承时,要继承的接口之间用逗号分隔。
例如,下面代码继承了3个接口:
然而,如果两个接口成员实现的是不同的功能,那么可能会导致其中一个接口的实现不正确或两个接口的实现都不正确。这时可以显式地实现接口成员,即创建一个仅通过该接口调用并且特定于该接口的类成员。显式接口成员是通过使用接口名称和一个句点命名该成员来实现的。
创建一个控制台应用程序,定义两个接口 ICalculate1 和 ICalculate2,在这两个接口中声明一个同名方法 Add;然后定义一个类 Compute,该类继承自已经定义的两个接口,在 Compute 类中实现接口中的方法时,由于 ICalculate1 和 ICalculate2 接口中声明的方法名相同,因此使用了显式接口成员实现;最后在主程序类 Program 的 Main 方法中使用接口对象调用 Add 方法执行相应的运算。代码如下:
显式接口成员实现中不能包含访问修饰符、abstract、virtual、override 或 static。例如,将上面实例中实现 ICalculate1 接口的 Add 方法的代码修改成如下形式:
图 1 显示接口成员实现中包含修饰符时出现的错误提示
抽象类和接口的区别主要有以下几点:
抽象类与接口的比较如下表所示。
在 C# 中,使用接口可以实现多重继承的功能。
C#接口的概念及声明
使用接口的程序设计人员必须严格遵守接口提出的约定。例如,在组装计算机时,主板与机箱之间就存在一种事先约定,不管什么型号或品牌的机箱、什么种类或品牌的主板,都必须遵守一定的标准来设计制造。因此在组装计算机时,计算机的零配件都可以安装在现今的大多数机箱上。接口就可以看作这种标准,它强制性地要求子类必须实现接口约定的规范,以保证子类必须拥有某些特性。
在 C# 中声明接口时需要使用 interface 关键字,其语法格式如下:
访问修饰符 interface 接口名称 [:继承的接口列表] { 接口内容; }说明,接口可以继承其他接口,类可以通过其继承的父类(或接口)多次继承同一个接口。
接口具有以下特征:
- 接口类似于抽象父类,继承接口的任何类型都必须实现接口的所有成员;
- 接口中不能包括构造函数,因此不能直接实例化接口;
- 接口可以包含属性、方法、索引器和事件;
- 接口中只能定义成员,不能实现成员;
- 接口中定义的成员不允许加访问修饰符,因为接口成员永远是公共的;
- 接口中的成员不能声明为虚拟或者静态成员。
例如,使用 interface 关键字定义一个 Information 接口,在该接口中声明 Code 和 Name 两个属性,分别表示编号和名称;然后声明一个方法 ShowInfo,用来输出信息。代码如下:
interface Information//定义接口 { string Code { get; set; }//编号属性及实现 string Name { get; set; }//名称属性及实现 void ShowInfo();//用来输出信息 }注意:接口中的成员默认是公共的,因此不允许加访问修饰符。
C#接口的实现与继承
接口通过类继承来实现,一个类虽然只能继承一个父类,但可以继承任意个接口。在声明实现接口的类时,需要在继承列表中包含所实现的接口的名称,多个接口之间用英文逗号分隔。通过继承接口实现输出进货信息和销售信息的功能,代码如下:
interface Information//定义接口 { string Code { get; set; }//编号属性 string Name { get; set; }//名称属性 void ShowInfo();//用来输出信息 } public class JHInfo : Information//继承接口,定义进货类 { string code = ""; string name = ""; public string Code//实现编号属性 { get { return code; } set { code = value; } } public string Name//实现名称属性 { get { return name; } set { name = value; } } public void ShowInfo()//实现方法,输出进货信息 { Console.WriteLine("进货信息:\n" + Code + " " + Name); } } public class XSInfo : Information//继承接口,定义销售类 { string code = ""; string name = ""; public string Code//实现编号属性 { get { return code; } set { code = value; } } public string Name//实现名称属性 { get { return name; } set { name = value; } } public void ShowInfo()//实现方法,输出销售信息 { Console.WriteLine("销售信息:\n" + Code + " " + Name); } } class Program { static void Main(string[] args) { Information[] Infos = { new JHInfo(), new XSInfo() };//定义接口数组 Infos[0].Code = "JH0001";//使用接口对象设置编号属性 Infos[0].Name = "笔记本电脑";//使用接口对象设置名称属性 Infos[0].ShowInfo();//输出进货信息 Infos[1].Code = "XS0001";//使用接口对象设置编号属性 Infos[1].Name = "华为荣耀V30";//使用接口对象设置名称属性 Infos[1].ShowInfo();//输出销售信息 Console.ReadLine(); } }上面代码接口中定义的属性并不会自动实现,只是提供了 get 访问器和 set 访问器,因此需要在子类中实现这两个属性。在子类中可以使用自动实现属性的方式实现这两个属性。
例如,在 JHInfo 类中实现 Code 和 Name 属性的代码可以修改成如下形式:
public string Code { get; set; } public string Name { get; set; }在 C# 中实现接口成员(显式接口成员实现除外)时,必须添加 public 修饰符,不能省略或者添加其他修饰符。
运行结果为:
进货信息:
JH0001 笔记本电脑
销售信息:
XS0001 华为荣耀V30
上面的实例中只继承了一个接口,接口还可以多重继承。使用多重继承时,要继承的接口之间用逗号分隔。
例如,下面代码继承了3个接口:
interface ITest1 { } interface ITest2 { } interface ITest3 { } class Test : ITest1, ITest2, ITest3 //继承3个接口,接口之间用逗号分隔 { }
C#显式接口成员实现
如果类继承了两个接口,并且这两个接口包含具有相同签名的成员,那么在类中实现该成员将导致两个接口都使用该成员作为它们的实现。然而,如果两个接口成员实现的是不同的功能,那么可能会导致其中一个接口的实现不正确或两个接口的实现都不正确。这时可以显式地实现接口成员,即创建一个仅通过该接口调用并且特定于该接口的类成员。显式接口成员是通过使用接口名称和一个句点命名该成员来实现的。
创建一个控制台应用程序,定义两个接口 ICalculate1 和 ICalculate2,在这两个接口中声明一个同名方法 Add;然后定义一个类 Compute,该类继承自已经定义的两个接口,在 Compute 类中实现接口中的方法时,由于 ICalculate1 和 ICalculate2 接口中声明的方法名相同,因此使用了显式接口成员实现;最后在主程序类 Program 的 Main 方法中使用接口对象调用 Add 方法执行相应的运算。代码如下:
interface ICalculate1 { int Add();//求和方法,加法运算的和 } interface ICalculate2 { int Add();//求和方法,加法运算的和 } class Compute : ICalculate1, ICalculate2//继承接口 { int ICalculate1.Add()//显式接口成员实现 { int x = 10; int y = 40; return x + y; } int ICalculate2.Add()//显式接口成员实现 { int x = 10; int y = 40; int z = 50; return x + y + z; } } class Program { static void Main(string[] args) { Compute compute = new Compute();//创建接口子类的对象 ICalculate1 Cal1 = compute;//使用接口子类的对象实例化接口 Console.WriteLine(Cal1.Add());//使用接口对象调用方法 ICalculate2 Cal2 = compute;//使用接口子类的对象实例化接口 Console.WriteLine(Cal2.Add());//使用接口对象调用方法 Console.ReadLine(); } }程序运行结果为:
50
100
显式接口成员实现中不能包含访问修饰符、abstract、virtual、override 或 static。例如,将上面实例中实现 ICalculate1 接口的 Add 方法的代码修改成如下形式:
public int ICalculate1.Add() { int x = 10; int y = 40; return x + y; }会出现图 1 所示的错误提示:
图 1 显示接口成员实现中包含修饰符时出现的错误提示
C#抽象类与接口
抽象类和接口都包含可以由子类继承实现的成员,但抽象类是对根源的抽象,而接口是对动作的抽象。抽象类和接口的区别主要有以下几点:
- 子类只能继承一个抽象类,但可以继承多个接口;
- 抽象类中可以定义成员的实现,但接口中不可以;
- 抽象类中可以包含字段、构造函数、析构函数、静态成员或常量等,接口中不可以;
- 抽象类中的成员可以添加访问修饰符;但接口中的成员默认是公共的,定义时不能加修饰符;
抽象类与接口的比较如下表所示。
比较项 | 抽象类 | 接 口 |
---|---|---|
方法 | 可以有非抽象方法 | 所有方法都是抽象方法,但不加 abstract 关键字 |
属性 | 可以自定义属性,并且可以实现 | 只能定义,不能实现 |
构造方法 | 有构造方法 | 没有构造方法 |
继承 | 一个类只能继承一个父类 | 一个类可以同时继承多个接口 |
被继承 | 一个类只能继承一个父类 | 一个接口可以同时继承多个接口 |
可访问性 | 类中的成员可以添加访问修饰 | 不能添加访问修饰符,默认都是 public |