1、接口对一组方法签名进行了统一命名。接口还能定义事件、无参属性和有参属性(C#的索引器)。
2、c#禁止接口定义任何一种静态成员。
3、C#编译器要求将实现接口的方法标记为public。CLR要求将接口的方法标记为virtual。不将方法显示标记为virtual,编译器会将它们标记为virtual和sealed;这会阻止派生类重写接口方法。将方法显式标记为virtual,编译器就会将方法标记为virtual,使派生类能重写它。派生类不能重写sealed的接口方法。但派生类可重新继承同一个接口,变为接口方法提供自己的实现:
1 //这个类派生自Object,它实现了IDisposable 2 internal class Derived:Base,IDisposable{ 3 //这个方法不能重写Base的Dispose, 4 //'new'表明该方法重新实现了IDisposable的Dispose方法 5 new public void Dispose(){ 6 Console.WriteLine("Derived`s Dispose"); 7 //注意,下面这行代码展示了如何调用基类的实现(如果需要的话) 8 //base.Dispose(); 9 } 10 }
4、隐式和显示接口方法实现
类型加载到CLR中时,会为该类型创建并初始化一个方法表。在这个方法表中,类型引入的每个新方法都有对应的记录项;另外,还为该类型继承的所有虚方法添加了记录项。继承的虚方法既有继承层次结构中的各个基类型定义的,也有接口类型定义的。
internal sealed class SimpleType:IDisplsable{ public void Dispose(){ Console.WriteLine("Dispose"); } }
类型的方法表将包含以下方法的记录项。
- Object(隐式继承的基类)定义的所有虚实例方法。
- IDisposable(继承的接口)定义的所有接口方法。本例只有一个方法,即Dispose,因为IDisposable接口只定义了这个方法。
- SimpleType引入的新方法Dispose。
为了简化编程,C#编译器假定SimpleType引入的Dispose方法是对IDisposable的Dispose方法的实现。之所以这样假定,是由于Dispose方法的可访问性public,而接口方法的签名和新引入的方法完全一致。也就是说,两个方法具有相同的参数和返回类型。如果新的Dispose方法被标记为virtual,C#编译器仍然认为方法匹配接口方法。C#编译器将新方法和接口方法匹配起来之后,会生成元数据,指明SimpleType类型的方法表中的两个记录项应引用同一个实现。
现在重新SimpleType,以便于看出区别:
internal sealed class SimpleType:IDisposable{ public void Dispose(){ Console.WriteLine("public Dispose"); } void IDisposable.Dispose(){ Console.WriteLine("IDisposable Dispose"); } }
在C#中,将定义方法的那个接口的名称方法名前缀(例如IDisposable.Dispose),就会创建显示接口方法实现(EIMI)。注意,C#中不允许在定义显示接口方法时指定可访问性(比如public或private)。
5、泛型和接口约束
泛型类型参数约束的好处。
第一个好处在于,可将泛型类型参数约束为多个接口。这样一来,传递的参数的类型必须实现全部接口约束。
//M的类型参数T被约束为只支持同时实现了 //IComparable和IConvertitle接口的类型 private static Int32 M<T>(T t)where T:IComparable,IConvertible{ ... }
接口约束的第二个好处是传递值类型的实例时减少装箱。
C#编译器为接口约束生成特殊IL指令,导致直接在值类型上调用接口方法而不是装箱。不用接口约束便没有其他办法让C#编译器生成这些IL指令,如此一来,在值类型上调用接口方法总是发生装箱。一个例外是如果值类型实现了一个接口方法,在值类型的实例上调用这个方法不会造成值类型的实例装箱。
6、设计:基类还是接口
- IS-A对比CAN-DO关系
类型只能继承一个实现。如果派生类型和基类型建立不起IS-A关系,就不用基类而用接口。接口意味着CAN-DO关系。如果多种对象类型都“能”做某事,就为它们创建接口。例如,一个类型能将自己的实例转换为另一个类型(IConvertible),一个类型能序列化自己的实例(ISerializable)。注意,值类型必须从System.ValueType派生,所以不能从一个任意的基类派生。这时必须使用CAN-DO关系并定义接口。
- 易用性
基类派生比接口容易得多。 - 一致性实现
- 版本控制