接口
C#中的接口(interface)在语法上有些类似与抽象类(abstract class),它定义了若干个抽象方法、属性、索引器、事件,形成一个抽象成员的集合,每个成员通常反映事物某些方面的功能。接口在本质上是对某方面功能或特征的约定。
程序中的接口的用处主要体现在下面几个方面:
- 通过接口可以实现不相关类的相同行为,而不需要考虑这些类之间的层次关系。
- 通过接口可以指明多个类需要实现的方法。
- 通过接口可以了解对象的交互界面,而不需要了解对象所对应的类。
定义接口
定义接口使用interface
关键字,在接口中可以有多个成员。
一个接口的成员必须是抽象的方法、属性、事件或索引器,这些抽象成员都没有实现体。一个接口不能包含常数、字段、操作符、构造函数、静态构造函数或嵌套类型,也不能包括任何类型的静态成员。
所有接口成员隐含的都有公共访问性,即隐含是public
的。但是,接口成员声明中不能使用除new
以外的任何修饰符。但接口本身可以带修饰符,如public
,interface
。按照编码惯例,接口的名字都以大写字母I开始。例:
public interface IStringList
{
void Add(string s);
int Count{
get;}
string this[int index]{
get; set;}
}
该接口中包含了一个方法、一个属性和一个索引器。
一个接口可从一个或多个基接口(父接口)中继承。例:
interface IMyInterface:IBase1, IBase2
{
void MethodA();
void MethodB();
}
子接口将继承所有父接口中的属性和方法。也可以利用new
修饰符来隐藏父接口中的成员。
例如以下定义(System.Collections.IList
的定义):
public interface IList:ICollection, IEnumerable
{
bool IsFixedSize{
get;}
bool IsReadOnly{
get;}
object this[int index]{
get; set;}
int Add(object value);
void Clear();
bool Contains(object value);
int IndexOf(object value);
void Insert(int index, object value);
void Remove(object value);
void RemoveAt(int index);
}
实现接口
接口的声明仅仅给出了抽象方法,相当于程序开发早期的一组协议,而具体地实现接口所规定的的功能,则需某个类为接口中的抽象方法书写语句并定义实在的方法体。
接口可以被类(class)来实现,也可以被结构体(struct)来实现。以下为用类来实现接口的格式:
class 类名:[父类,] 接口, 接口, ..., 接口
{
...
}
一个类在实现接口实,要注意以下问题:
- 在类的声明部分,用冒号(
:
)表明其父类及要实现的接口,其中父类一定要放到接口名的前面。如果父类省略,则隐含父类为System.Object
。 - 如果一个类实现了某个接口,则要求一定能在该类中找到与该接口的各个成员相对应的成员,也能找到该接口所有的父接口的所有成员。当然,这样的成员可以是在本类中定义的,也可以是从本类的父类中继承过来的。
- 一个抽象类实现接口时,也要求为所有成员提供实现程序,抽象类可以把接口方法映射到抽象方法中。
- 一个类只能有一个父类,但是它可以同时实现若干个接口。一个类实现多个接口时,如果把接口理解成特殊的类,那么这个类利用接口实际上就获得了多个父类,即实现了多重继承。
- 特别注意,接口的抽象方法的访问控制都隐含为
public
,所以类在实现方法时,必须使用public
修饰符。
示例:
using System;
namespace ConsoleApp3接口
{
interface Runner
{
void run ();
}
interface Swimmer
{
void swim ();
}
abstract class Animal
{
abstract public void eat ();
}
class Person : Animal, Runner, Swimmer
{
public void run () {
Console.WriteLine ("run");
}
public void swim () {
Console.WriteLine ("swim");
}
public override void eat () {
Console.WriteLine ("eat");
}
public void speak () {
Console.WriteLine ("speak");
}
}
class TestInterface
{
static void m1 (Runner r) {
r.run ();
}
static void m2 (Swimmer s) {
s.swim ();
}
static void m3 (Animal a) {
a.eat ();
}
static void m4 (Person p) {
p.speak ();
}
public static void Main (string[] args) {
Person p = new Person ();
m1 (p);
m2 (p);
m3 (p);
m4 (p);
Runner a = new Person ();
a.run ();
}
}
}
对接口的引用
接口可以作为一种引用类型来使用,通过这些引用型变量可以访问类所实现的接口中的方法。例如:假如Person
类实现了ISwimmable
,则可以将Person
对象作为ISwimmable
来引用:
Person p = new Person();
ISwimmable s = p;
同样,如果一个方法需要用一个接口作为参数,则可以直接将一个实现了该接口的类的实例对象传入。把接口作为一种数据类型可以不需要了解对象所对应的具体的类,而着重于它的交互界面或功能。
接口可能有多个父接口,而接口内的成员可能有同名现象,所以,在对接口成员进行调用时,可能出现不明确的现象。例如:
interface IList
{
int Count{
get; set;}
}
interface ICounter
{
void Count(int i);
}
interface IListCounter : IList, ICounter{
}
class C
{
void Test(IListCounter x){
x.Count(1); // Error, Count不明确
x.Count = 1; // Error, Count不明确
((IList)x).Count = 1; // Ok, 调用IList.Count.set
((ICounter)x).Count(1); // Ok, 调用ICounter.Count
}
}
显示接口成员实现
有时候,多个接口有相同的签名方法(或其他成员),如果一个类要同时实现多个接口,可以只用一个方法即可满足各个接口的要求。然而,在更多的情况下,这些相同签名方法的在各个接口中的含义并不相同,所以要求类在实现各个接口时,要显示指明实现的是哪个接口中的方法。例如:
using System;
namespace ConsoleApp3接口
{
class InterfaceExplicitImpl
{
static void Main (string[] args) {
FileViewer f = new FileViewer ();
f.Test ();
((IWindow)f).Close ();
IWindow w = new FileViewer ();
w.Close ();
}
}
interface IWindow
{
void Close ();
}
interface IFileHander
{
void Close ();
}
class FileViewer : IWindow, IFileHander
{
void IWindow.Close () {
Console.WriteLine ("Window Closed");
}
void IFileHander.Close () {
Console.WriteLine ("File Closed");
}
public void Test () {
((IWindow)this).Close ();
}
}
}
从以上可以看出,显示接口成员实现程序跟其他成员相比,具有不同的访问能力特性。因为显示接口成员不能通过类实例来调用,从这个意义上说它们是私有的;而另一方面,它们可以通过一个接口实例来访问,从这个意义上说,它们又是公共的。
显示接口成员实现主要服务于两个目的:
- 因为显示接口成员不能通过类或结构实例进行访问,只允许通过接口进行访问,所以,经常用语只能通过接口进行访问的情形。
- 显示接口成员实现 程序允许用相同的签名消除接口成员的歧义,使得一个类或结构可以包含签名相同而返回类型不同的成员。