欢迎来到.net学习网

欢迎联系站长一起更新本网站!QQ:879621940

您当前所在位置:首页 »  .NET本质论第一卷:公共语言运行库教程 » 正文

本教程章节列表

类型和接口

创建时间:2012年08月29日 16:49  阅读次数:(5553)
分享到:

CTS类型已经做到很高程度的结构化,到目前为止,关于它们的讨论主要限于其组织在一起的方式。类型语义的问题还没有涉及。现在看看类型是如何传达语义的,让我们从类型的类别(categorization)(?在这一节中,读者可能会被类型(type)、类(class)、类别(categorization)等词所迷惑。我们前面已经说过,类型是程序的生成块。对.NET而言,类型分为值类型、引用类型(还有指针类型,用于非托管代码)。类也是一种类型,即类类型,它是引用类型之一。这里的类别,则是作者引入的概念,主要是说对不同的类型可以进行分门别类,这主要是从语义的角度(当然类别本身可能就是类型,如接口)。例如,human(人)和dog(狗),他们是两个不同的类型,但有一些动作是相同的,比如,吃(eat)。于是,我们可以从这个角度,将他们划为一个类别)开始吧。

我们经常需要根据两个或更多的类型所设的公共假设,将类型划分成不同的类别。这种归类相当于类型的附加文档,因为只有显式地声明属于这个类别的类型,才被认为是可以共享该类别中所隐含的假设。在CLR中,将这些类型的类别称为接口interface)。接口是整合到类型系统中的类型归类。因为接口代表的类别自身就是类型,所以,你可以声明字段(和变量,以及方法参数)来获取类别的从属关系,而不是对要用到的实际的具体类型进行硬编码(hard-code)。这种松散的要求允许在实现上的可替代性,它是多态(polymorphism)的基石。

从结构上说,接口是CLR的另外一种类型。接口有类型名,它可以有成员,其限制条件就是它既不能有实例字段,也不能有带实现的实例方法。从结构上说,接口与其他类型的真正区别是,在类型的元数据上是否存在interface特性。在CLR中使用接口的语义是特别规定的。

接口是形成分类或类型家族的抽象类型。对接口类型的变量、字段和参数进行声明是合法的。但实例化一个仅仅基于接口的对象是不合法的(即接口不能用像通常类型一样用new 来实例化)。更进一步地说,接口类型的变量、字段和参数必须引用一个具体类型的实例,而这个具体实例则必须显式地被声明与该接口兼容。
下面的例子说明了接口的重要性。
public sealed class AmericanPerson {}
public sealed class CanadianPerson {}
public sealed class Turnip {}
class Quack {
void OperateAndTransfuseBlood(Object patient) {
// 假如芜青被当成了病人,怎么办?
}
}

在这个例子中,Quack.OperateAndTransfuseBlood方法接受单个System.Object类型的参数。System.Object类型是CLR中的通用类型;这意味着用它可以传递作为参数值的任何类型的实例。在这个例子中,你可以将AmericanPerson、CanadianPerson和Turnip(?turnip:芜菁;芜菁甘蓝一种广泛地种植的产于欧亚大陆的植物 (芜菁 芸苔属),属十字花科,有大块的、多肉的、可食用的根,呈黄色成白色)的实例合法地传递给方法。然而,从给定方法的名字(方法名是OperateAndTransfuseBlood,意思是通用手术进行输血。美国人(AmericanPerson)和加拿大人(CanadianPerson)都能作为病人(patient),接受手术输血。但是芜菁(Turnip)是一种植物,不可能对其进行输血。因而,在这里尽管从编译上是没有问题,但这种潜在的错误类型,将在运行时出现问题)来看,如果传递的是Turnip实例,那么,它可能什么也做不了。由于参数类型没有对Turnip实例进行鉴别,因而,这种错误直到运行时才会被发现。
下面的例子说明了接口是如何解决这个问题。
public interface IPatient {}
public sealed class AmericanPerson : IPatient {}
public sealed class CanadianPerson : IPatient {}
public sealed class Turnip {}
class Doctor {
void OperateAndTransfuseBlood(IPatient patient) {
// 不允许Turnip!
}
}

在这个例子中,有一个叫做IPatient的类型的类别。这个类别被声明为接口。兼容IPatient接口的类型显式地声明了这种兼容性,并作为类型定义的一部分。AmericanPerson和CanadianPerson类型都这样做了。现在,OperateAndTransfuseBlood方法可以声明它只能接受兼容IPatient接口类型的参数。因为Turnip类型没有声明与IPatient接口兼容,所以,如果试图传递Turnip对象给这个方法,就会导致编译时的失败。这种解决方案较好而简单地提供了两个显式的方法重载——一个用于AmericanPerson,另一个用于CanadianPerson。这种解决之道可以让你传递新的类型给Doctor.OperateAndTransfueBlood方法,而不需要显式地定义新的重载函数。
一个类型声明兼容多个接口是合法的。当一个具体类型(例如,一个类)声明兼容多个接口,这说明这个类型的实例可在多个上下文中使用。例如,示例3.5的类型AmericanPerson声明兼容IPatient和IBillee两者,表明它既可以是一个patient(病人),也可以是一个billee(付费方)。在这个示例中,CanadianPerson声明只兼容IPatient,如果他也需要一个付费方的话,将需要另一个类型[要么是CanadianGovernment(加拿大政府),要么是AmericanPerson(美国人)]的一个实例。
示例3.5:支持多个接口
public interface IPatient { }
public interface IBillee { }
public sealed class AmericanPerson : IPatient, IBillee {}
public sealed class CanadianPerson : IPatient {}
public sealed class CanadianGovernment : IBillee {}
class Doctor {
// 对于这两个参数(美国人既可以是病人,也可以是付费方),美国病人都是可接受的
void OperateAndTransfuseBlood(IPatient patient,
IBillee moneySrc)
{
}
}

你可以将接口视为将所有可能对象的集合划分成不同的子集。对象属于哪些子集,取决于对象的类型是否声明兼容那些接口。图3.2就是由此而来,它显示了在示例3.5中所定义的类型。由此类推,一个接口类型也可能被声明为兼容一个或者更多的接口。这样做的结果是,所有声明与新接口兼容的类型必须同时兼容额外(这个新接口类型直接兼容的接口类型)的接口。当你声明兼容这种新的接口时,大多数语言(例如,C#、VB.NET)将隐式地作这种判断。


图3.2:作为子集的接口

考虑示例3.6所示的例子。在这个示例中,ISelfFundedPatient接口已经声明同时兼容IPatient接口和IBillee接口。这意味着声明兼容ISelfFundedPatient接口的类型(例如,WealthyPerson)必须同时兼容IPatient和IBillee。但这并不是说,所有兼容IPatient和IBillee的类型都会兼容ISelfFundedPatient。在本例中,类型InsuredPerson的实例显然不允许作为参数传递给OperateAndTransfuseBlueBlood方法。这在图3.3种有说明。
示例3.6:多个接口的继承
public interface IPatient { }
public interface IBillee { }
public interface ISelfFundedPatient : IPatient, IBillee {}
public sealed class InsuredPerson : IPatient, IBillee {}
public sealed class WealthyPerson : ISelfFundedPatient {}
class Doctor {
// 接受任何类型的病人。这里,假设已经存在付费方
void OperateAndTransfuseBlood(IPatient p,
IBillee b,
string color) {
}
// 只接受有钱的病人
void OperateAndTransfuseBlueBlood(ISelfFundedPatient sfp){
OperateAndTransfuseBlood(sfp, sfp, "blue");
}
}

图3.3:接口继承

接口对还能够将显式的要求强加于兼容它的类型上。特别是接口包含抽象方法声明abstract method declarations)。这些方法声明相当于对支持该接口的所有类型的要求。如果一个具体类型声明兼容接口I,那么,这个具体类型必须提供接口I中的所有抽象方法的实现。
为了理解接口如何强制类型实现接口的方法,考虑下面用C#写的接口定义:
public interface IPatient {
void AddLimb();
void RemoveLimb();
}

对于声明兼容IPatient的所有具体类型,现在必须提供AddLimb和RemoveLimb方法的实现。而这些方法将匹配IPatient中声明的签名(方法的签名与方法的名称是不同的概念。方法签名由方法的名称和它的每一个形参(按从左到右的顺序)的类型和种类(值、引用或输出)组成)。
下面是一个实现了前面定义的IPatient接口的具体类型:
public sealed class AmericanPerson : IPatient {
internal int limbCount = 4;
public void AddLimb() { ++limbCount; }
public void RemoveLimb() { --limbCount; }
}

在这个例子中,IPatient接口的方法是具体类型的公共合同的一部分。CLR还允许具体类型将这些方法声明为private,它使用某种机制来标明这些方法是被用来满足接口的要求。例如,下面的实现从类型的公共合同中隐藏了RemoveLimb方法:
public sealed class CanadianPerson : IPatient {
internal int limbCount = 4;
public void AddLimb() { ++limbCount; }
void IPatient.RemoveLimb() { --limbCount; }
}

在这个示例中,只有AddLimb方法可以通过CanadianPerson类型的引用来访问。如果要访问RemoveLimb方法,就必须通过IPatient类型的引用,它能够访问这两个方法。
当通过基于接口的引用调用方法时,CLR将在运行时根据引用对象的具体类型,来决定实际调用的方法。这种动态方法分发dynamic method dispatch)是启用多态的一个必要的特征。多态将在第六章详细讨论。

来源:.net学习网
说明:所有来源为 .net学习网的文章均为原创,如有转载,请在转载处标注本页地址,谢谢!
【编辑:Wyf】

打赏

取消

感谢您的支持,我会做的更好!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

最新评论

共有评论0条
  • 暂无任何评论,请留下您对本文章的看法,共同参入讨论!
发表评论:
留言人:
内  容:
请输入问题 5+20=? 的结果(结果是:25)
结  果: