接口是指定一组函数成员而不实现它们的引用类型,所以只能类和结构来实现接口,可以在接口中声明方法、属性、事件、索引器,接口只包含了成员的声明,成员的定义是派生类的责任,接口提供了派生类应遵循的标准结构。
接口的声明规则
(1)接口声明不能包含成员变量(如public int i; 但可以包含属性如public int i{get; set;})、静态成员、构造函数、析构函数
(2)接口中函数成员的声明不能包含任何实现代码
(3)接口名称必须从大写的I开始
(4)与类和结构一样,接口声明还可以分隔成分部接口声明
(5)接口声明可以有任何的访问修饰符public、protected、internal或private
(6)接口的成员是隐式public的,不允许有任何的访问修饰符(包括public)
接口的实现规则
(1)在基类列表中包括接口名称
(2)因为接口没有构造函数,所以不能使用new来实例化,接口一旦被继承,实现类必须实现接口中所有成员,除非是被另一个接口继承
(3)实现接口必须和接口的格式一致(不仅是名字),因为接口成员隐式是public的,所以在实现时也只能只用public来修饰
(4)当一个接口继承自另一个接口时,如果俩个接口中有相同的方法,可用new关键字隐藏父接口中的方法
interface I1
{
void foo();
}
interface I2 : I1
{
new void foo();
}
(5)如果类既继承了一个基类(也只能继承一个基类),也实现了接口,则基类列表中基类的名称必须放在所有接口之前
接口是引用类型
接口不仅仅是类或结构要实现的成员列表,它是一个引用类型,我们不能直接通过类对象的成员访问接口,然而,我们可以通过把类对象引用强制转换为接口类型来获取指向接口的引用,一旦有了接口的引用,就可以使用点号来调用接口的方法。如下面的代码中,a为类对象,a.foo();只是调用类对象的实现方法,而对其强转为I1接口类型的引用后,在调用i.foo();则是调用接口中的引用方法,虽然效果是一样的都输出999,但是俩者是不一样的。
接口和as运算符
使用as运算符来获取对象接口的引用比使用强制转换运算符更加安全,如果我们尝试将类对象引用强制转换为类未实现的接口的引用,强制转换操作会抛出一个异常,我们可以通过使用as运算符来避免这个问题,如下代码所示,如果类实现了接口,表达式返回指向接口的引用,如果没有实现接口,表达式返回null而不是抛出异常。
interface I1
{
void foo();
}
interface I2
{
void fun();
}
class A : I1
{
public void foo()
{
Console.WriteLine(999);
}
}
class Program
{
static void Main(string[] args)
{
A a = new A(); //创建类对象
a.foo(); //999 //调用类对象的实现方法
I1 i = (I1)a; //将类对象的引用转换为接口类型的引用
i.foo(); //调用引用方法
I1 ii = new A(); //直接创建并转换为接口类型
ii.foo();
//I2 i2 = (I2)a; 抛出异常
I2 i2 = a as I2;
if (i2 != null)
i2.fun();
else
Console.WriteLine("错误!"); //打印出错误
}
}
派生成员作为接口的实现
实现接口的类可以使用从它的基类继承来的函数,来代替去显示实现接口中函数。
接口的显式实现
之前所用的接口实现方法均为隐式实现,这也是我们使用的较多的一种实现方式,接口也可以以显式的方式来实现,它使用限定接口名称来声明,由接口名称和成员名称以及它们中间的点分隔符号构成,此时不能添加修饰限定符。而且在调用的时候不能像以前那样通过类的实例点出来,而是需要在之前将其装换为相应的接口,然后再去调用。
interface I1
{
void foo();
}
interface I2
{
void foo();
}
class A : I1,I2
{
void I1.foo() //public void I1.foo()会编译出错
{
Console.WriteLine("I1.foo");
}
void I2.foo()
{
Console.WriteLine("I2.foo");
}
public void fun()
{
//foo(); 直接调用会出错
((I1)this).foo(); //需要利用到this来强制转换为接口
}
}
class Program
{
static void Main(string[] args)
{
A a = new A();
//a.foo(); 直接调用则编译出错
((I1)a).foo(); //I1.foo 必须显示获取对应接口的引用来调用
((I2)a).foo(); //I2.foo
a.fun(); //I1.foo
}
}
显式实现与隐式实现的区别:
(1)隐式实现:接口和类都可以访问
(2)显式实现:只有接口可以访问
显式实现的好处:
(1)隐藏代码的实现(当使用类的实例的时候,没有办法直到它有实现的接口的方法)
(2)在使用接口访问的系统中,限制调用者只能通过接口调用而不是底层的类来访问
接口和抽象类的区别
抽象类在某种程度上与接口类似,但是他们大多只是用在只有少数方法由基类声明并由派生类实现的情况。
(1)接口用于规范,抽象类用于共性,抽象类是类只能被单继承,接口可以一次实现多个
(2)接口中只能声明方法、属性、事件、索引器,而抽象类中还可以有方法的实现、字段、构造函数、析构函数、静态成员、常量
(3)在抽象类中加入一个方法,那么它的子类就同时有了这个方法,而在接口中加入新的方法,那么实现它的类就要重新编写(这就是为什么说接口是一个类的规范了)
(4)接口成员被定义为public的,但抽象类的成员可以是private、protected、internal、protected internal的内部成员
(5)我们在vs中实现接口时会发现有2个选项,一个是实现接口,一个是显示实现接口,实现接口(隐式)就是正常常用的方式,显式实现接口的话,实现的方法时属于接口的,而不是属于实现类的。