c#中的接口,有隐式接口【就是一般意义上直接实现某个接口中的方法,从表现上看,没有接口名称限定】,和显式接口【有接口名称限定】
internal interface IDemo{
void func();
}
internal sealed class Demo : IDemo{
//隐式实现接口
public void func(){
...
}
//显式接口【注意没有public/private修饰】
void IDemo.func(){
...
}
}
一般说来,想当然的,我们认为如果一个类同时实现了多个接口【而这些接口中恰有一些,它们有着相同的函数签名】,那么我们需要显式实现这些接口【也就是用接口类型名称限定】这是没有错的。但是不仅仅如此,看下面这个例子,如果觉得有点“意思”再继续细究它:
internal interface ITest{
void func();
}
internal sealed class Test : ITest{
void ITest.func(){
...
}
}
//Main
Test t = new Test();
t.func();
这段代码中有问题吗?它是无法通过编译的。
错误: “Test”不包含“func”的定义,
并且找不到可接受类型为“ConsoleApplication3.Demo”的第一个参数的扩展方法
“func”(是否缺少 using 指令或程序集引用?)
为什么会这样?Test中不是明明实现了func吗?这里应该引起我们的注意【c++/java语言中没有显式接口这种概念】
如果写成:
ITest t = new Test();
t.func();
//或者
Test t = new Test();
((ITest)t).func();
又正常了。
再看一段:
internal interface ITest{
void func();
}
internal sealed class Test : ITest{
void ITest.func(){
System.Console.WriteLine("interface ITest func");
}
public void func(){
System.Console.WriteLine("class Test func");
}
}
//Main
ITest it = new Test();
it.func()//输出 interface ITest func
Test t = new Test();
t.func();//输出 class Test func
希望这样的结果不会让【习惯了java/c++的人】觉得诧异。
再回头仔细看看,发现,对于接口的显式实现,没有用public/private等进行修饰【如果修饰了,无法通过编译】。原因是: 编译器在生成方法的元数据时,将其可访问性设为private,从而防止其他代码在使用类的实例时直接调用接口方法。要调用接口方法,只能通过接口类型的一个变量来进行。【这是强制”面向接口编程“吗- - ,不过感觉确实不咋地】。并且不能用virtual来修饰显式接口实现【显式接口实现并非真的是类型的对象模型的一部分,它是将一个接口(一组行为或者方法)连接到一个类型上,同时避免公开行为/方法的一种方式。
内部原理是:类型加载到CLR中时,会创建一个方法表,类型引入的每个新方法都有对应的记录【同时也为继承/实现的方法添加了记录】,那么当我们只进行了隐式实现时,接口方法【声明】记录和类中新方法【实现】 都指向了这个新方法实现。【从而导致,无论是使用接口类型还是实现类类型,调用的方法结果一样】,但是,如果此时进行了显式接口实现,那么接口方法就会指向这个显式实现,而类中隐式实现的【新方法】指向的仍然是隐式实现。【这就能理解上面这个例子的输出了,有点绕,- - 】
如果,我是说如果,一个类实现了多个接口,并且多个接口中有相同的签名【返回值,函数名,参数一致】,那么这种显式接口实现语义无可厚非【也只能显式接口实现,此时如果也进行了隐式接口实现,那么这个隐式实现是可有可无的(显式接口实现必须有),如果用实现类型去调用,就会调用这个隐式实现(如果你实现了的话)】,但是如果仅仅是实现一个接口,那么这种语义确实很容易让人慌乱。
如果一个类只实现一个接口,那么干嘛使用显式接口实现啊,直接用隐式实现不就可以了吗?也不尽然,即使只是实现一个接口,有时候也是需要使用到显式接口实现的。
看个例子:
public interface IComparable{
int CompareTo(Object o);
}
//像下面这样定义一个类型去实现它
internal struct SomeValueType : IComparable{
private int m_x;
public SomeValueType(int x){this.m_x = x;}
public int CompareTo(Object o){
return (m_x - ((SomeValueType)other).m_x);
}
}
//Main
SomeValueType v = new SomeValueType();
Object o = new Object();
int n = v.CompareTo(v);//会发生装箱操作
n = v.CompareTo(o);//运行时异常【object无法转型为SomeValueType】
上面,一个是发生了装箱操作【注意,SomeValueType是struct不是class 定义的】
还有就是运行时异常。这两点确实不怎么好。那么就可以用显式接口实现来试试看:
internal struct SomeValueType : IComparable{
private int m_x;
public SomeValueType(int x){this.m_x = x;}
public int CompareTo(SomeValueType other){
return (m_x - other.m_x);
}
//由于上面那个修改了参数类型,所以还需要实现接口【显式实现】
//我自己无意尝试了下,如果隐式实现呢,那么是实现了接口的方法,可以通过编译
//但是就没有什么意义了【因为对于传入Object类型,依然会调用(如果实现)这里
//的隐式接口【还是会发生编译时异常】也就无法体现在这里显式接口实现的好处了。
int IComparable.CompareTo(Object o){
//委托给上面那个CompareTo(SomeValueType)实现
return CompareTo((SomeValueType)o);
}
}
//Main【没有任何变化,为了方便阅读和连贯,直接复制了一份】
SomeValueType v = new SomeValueType();
Object o = new Object();
int n = v.CompareTo(v);//不会发生装箱操作【调用参数为SomeValueType那个】
n = v.CompareTo(o);//编译时异常【object无法转型为SomeValueType】
//是显式实现接口【如果要调用接口方法,是需要使用接口类型变量的,
//而这里的v是SomeValueType,如果还不理解,看看本篇前面部分】
也可以通过泛型接口来实现【参见12 泛型】,这种实现本篇中就不做介绍了。
最后要注意的是:如果可能的话,尽量少用显式接口实现【毕竟语义不是那么明显】
c#编译器要求用于实现接口的方法标记为public,CLR会要求标记为virtual,如果在源代码中没有显式的标记virtual,那么编译器会标记为virtual和sealed【也就是派生类无法再重写这个实现类的这个方法】,如果源代码中自己标记了virtual【同时注意,此时不要让实现类有sealed修饰,否则出错】,那么方法就是非sealed【派生类可以重写】—-这里说的是对于接口的隐式方法实现和前面【显式接口实现是不允许标记virtual的】不冲突