1.接口与继承
将一个对象看成多个类型的能力通常称为多继承。CLR支持单实现继承和多接口继承。
CLR规定一个类型只能有一个基类型,System.Obejct是所有类型的最终基类型。这种继承称为实现继承(implementation inheritance),因为派生类型继承了基类型所有的行为和能力;派生类型可以有像基类型一样的行为。
接口继承(interface inheritance)意味着一个类型继承的是接口中的方法签名,而非方法实现。当一个类型继承了一个接口时,它只是在许诺提供其中的方法实现;如果类型没有提供接口方法的实现,那么类型将被认为是抽象的,从而不可能被执行实例化。
接口不会继承来自任何的System.Object派生类型。接口仅仅是一个包含着一组虚方法的抽象类型,其中每一个方法都有他们的名称、参数和返回类型。接口方法不能包括任何实现,因此接口时不完整的(抽象的)。注意接口中也可以定义事件、无参属性以及含参属性(C#中又称索引器),因为他们只不过是影射到方法上的语法缩写而已。CLR还允许接口包含静态方法、静态字段、常数、以及静态构造器。
根据约定。接口类型的名称要加一个大型的字母I前缀。接口定义允许使用修饰符--public、protected、internal以及private。
虽然一个接口不能继承其他类型的实现,但是它可以“继承”其他接口所约定的合同(ICollection就继承了IEnumberalbe)。实际上,一个接口可以包含多个接口的合同。
例如,string类型不需要实现他的基类Object提供的方法,它可以简单地将这些方法继承下来就可以了。但是,string类型必须实现所有接口中声明的方法。否则,它将是一个不完整的类型(抽象类型)。
我们可以将一个对象从一个接口转型为另一个接口,只要改对象的类型同时实现了两个接口。
当我们考虑设计一个基类型,还是一个接口类型时,下面是一些指导原则
(1)IS-A与CAN-DO关系 一个类型仅可以继承一个实现。如果派生类型不能和基类构成一个IS-A关系,那么就不要使用基类型,而应该采用接口。接口隐含有一个CAN-DO关系。如果认为CAN-DO功能时多个对象类型,茂也应该采用接口。
(2)易用性 对于开发人员说,通过继承一个基类型来定义新的的类型要比创建一个接口更为方便。基类型可以提供很多功能,这样我们可以在派生类型中只需对她的行为做一下少量改动接口,但是如果是接口,新类型就必须实现其所有的成员。
(3)一致的实现 不管一个接口合同在文档中记录的有多好,要每个人都100%正确的实现它是不可能的。
(4)版本 如果我们向一个基类型中添加一个方法,那么其派生类型将自动继承新成员的默认实现。而向接口中添加新的成员将会强制要求接口的用户修改源代码,并重新编译。
2.设计支持插件组件的应用程序
当我们创建可扩展的应用程序时,接口应该处于中心位置。假设我们正在编写一个应用程序,并且希望其他人创建的类型能够被我们的应用程序无缝地加载和使用,下面是提供设计这样程序的方法。
(1)创建一个程序集,然后在其中定义接口,接口的方法将用于应用程序和插件组件的通信机制。在为接口方法定义参数和返回值时,我们应该尽可能地使用定义在MSCorLib.dll中的其他接口和类型。但如果确实希望传递。或者返回我们自己第一的数据类型时,则应该把他们也定义在改程序集中。一旦建立好接口定义后,我们应该给该程序集指定一个强命名,然后将其打包并部署到合作伙伴和用户那里。这样以后就把程序集是作为一个恒定不变的程序集(恒定不变是指其内容不会改变)
(2)创建一个单独的程序集用于包含我们的应用程序所使用的其他类型。该程序集显然要引用到前一个程序集定义的接口和类型。我们可以按照我们的意愿任意改变程序集中的代码,因为产假组件不会引用到该程序,所以如果需要的话,我们甚至可以每隔一个小时提供一个该程序集的版本。
(3)插件开发人员当然会在他们的程序集中定义自己的类型。他们的程序集也将会引用到我们前面定义的接口程序集的类型。插件开发人员也可以随时提供新版的程序集,我们的应该程序则可以继续正常使用这些新版的插件组件,而不会遇到任何问题。
3.我们可以使用接口类改变一个已装箱值类型中的字段。
4.实现多个有相同方法的接口
有时我们会发现一个类型需要实现多个接口,而且他们们的方法有有着相同的名称和签名。
我们只能使用完全限定接口名的办法来实现接口,当我们子在一个类型中用完全限定接口名俩定义一个接口时,该方法被认为是私有的方法,因此我们不能使用类型本身的引用类调用它。但是,当我们将该类型的引用转型为一个接口时,该接口中定义的方法将可以被调用,这时它又成为一个共有的方法。
5.显式接口成员实现(explicit interface member inplementation)
可以在接口实现时,把一下运行时的错误改变成编译时的错误。
我们看个例子:
public interface IComparable()
{
Int32 CompareTo(Object other)
}
struct SomeValueType :IComparable
{
private Int32 x;
private SomeValueType(Int32 x){this.x=x;}
public Int32 CompareTo(Object other)
{
return (x-((SomeValueType)other).x);
}
测试代码:
static void Main()
{
SomeValueType v=new SomeValueType(0);
Object o=new Object();
Int32 n=v.CompareTo(o);
}
}
这段代码编译时不会有错误,但是当运行时,会抛出一个InvalidCastException的异常
为了把运行时的错误该成编译时的错误,我们重新实现了SoemValueType
struct SomeValueType :IComparable
{
private Int32 x;
private SomeValueType(Int32 x){this.x=x;}
public Int32 CompareTo(SomeValueType other)
{
return (x-other.x);
}
//注意,该方法没有public/protected 并且使用了完全限定
Int32 IComparable.CompareTo(Object other)
{
return (x-((SomeValueType)other).x);
}
当在类型上调用时,我们调用的是public的那个CompartTo,因为在类型上,我们认为接口中的CompareTo是私有的,而如果我们在接口上调用CompareTo的话,我们是调用的IComparable:CompareTo,这时接口中的CompareTo认为是共有的。