CLR是完全围绕类型展开的,这一点到现在为止应该很明显了。类型为应用程序和其他类型公开了功能。通过类型,用一种编程语言写的代码能与用另一种语言写的代码沟通。由于类型是CLR的根本,所以Microsoft制定了一个正式的规范,叫做“通用类型系统”(Common Type System,CTS),它描述了类型的定义和行为。
注意:事实上,Microsoft已将CTS和.NET Framework的其他组件--包括文件格式、元数据、中间语言以及对底层平台的访问(P/Invoke)--提交给ECMA以完成标准化工作。最后形成的标准称为“公共语言基础结构”(Common Language Infrastructure,CLI)。除此之外,Microsoft还提交了Framework类库的一部分、C#编程语言以及C++/CLI编程语言。欲知这些工业标准的详情,请访问ECMA的Technical Committee 39专题网站:http://www.ecma-international.org/。还可访问Microsoft自己的专题网站:http://msdn.microsoft.com/en-us/netframework/aa569283.aspx。除此之外,Microsoft还就ECMA-334和ECMA-335规范进行了社区承诺(Community Promise),详情请访问http://www.microsoft.com/interop/cp/default.mspx。
CTS规范规定,一个类型可以包含零个或者多个成员。本书第II部分“设计类型”将更详细地讨论这些成员。目前只是简单地介绍一下它们。
- 字段(Field) 一个数据变量,是对象状态的一部分。字段根据名称和类型来区分。
- 方法(Method) 一个函数,能针对对象执行一个操作,通常会改变对象的状态。方法有一个名称、一个签名以及一个或多个修饰符。签名指定参数的数量(及其顺序);参数的类型;方法是否有返回值;如果有返回值,还要指定返回值的类型。
- 属性(Property) 对于调用者,该成员看起来像是一个字段。但对于类型的实现者,它看起来像是一个方法(或者两个方法,称为getter和setter,或者称为取值方法和赋值方法)。属性允许实现者在访问值之前对输入参数和对象状态进行校验,以及/或者只有在必要的时候才计算一个值。属性还允许类型的用户采用简化的语法。最后,可利用属性创建只读或只写的“字段”。
- 事件(Event) 事件在对象以及其他相关对象之间实现了一个通知机制。例如,利用按钮提供的一个事件,可以在按钮被单击之后通知其他对象。
CTS还指定了类型可视化规则以及类型成员的访问规则。例如,如果将类型标记为public(在C#中使用public修饰符),任何程序集都能看见并访问该类型。但是,如果将类型标记为assembly(在C#中使用internal修饰符),只有同一个程序集中的代码才能看见并访问该类型。所以,利用CTS制定的规则,程序集为一个类型建立了可视边界,CLR则强制(贯彻)了这些规则。
调用者虽然能“看见”一个类型,但并不是说就能随心所欲地访问它。利用一下选项,可进一步限制调用者对类型中的成员的访问。
- private 成员只能由同一个类(class)类型中的其他成员访问。
- family 成员可由派生类型访问,不管那些类型是否在同一个程序集中。注意,许多语言(比如C++和C#)都用protected修饰符来表示family。
- family and assembly 成员可由派生类型访问,但这些派生类型必须是在同一个程序集中定义的。许多语言(比如C#和Visual Basic)都没有提供这种访问控制。当然,IL汇编语言不在此列。
- assbmly 成员可由同一个程序集中的任何代码访问。许多语言都用internal修饰符来标识assembly。
- family or assembly 成员可由任何程序集中的派生类型访问。成员也可由同一个程序集中的任何类型访问。在C#中,是用protected internal修饰符来标识family or assembly。
- public 成员可由任何程序集中的任何代码访问。
除此之外,CTS还为类型继承、虚方法、对象生存期等定义了相应的规则。这些规则在设计之初,并顺应了可以用现代编程语言来表示的语义。事实上,根本不需要专门去学习CTS规则本身,因为你选择的语言会采用你熟悉的方式公开它自己的语言语法与类型规则。通过编译来生成程序集时,它会将语言特有的语法映射到IL--也就是CLR的“语言”。
接触CLR后不久,我便意识到最好区别对待“代码的语言”和“代码的行为”。使用C++,可定义自己的类型,这些类型有它们自己的成员。当然,也可使用C#或Visual Basic来定义相同的类型,并在其中添加相同的成员。使用的语言不同,用于定义类型的语法也不同。但是,无论使用哪一种语言,类型的行为都是完全一致的,因为最终是由CLR的CTS来定义类型的行为。
为了更形象地理解这一点,让我们来举一个例子。CTS规定一个类型只能从一个基类派生(单继承)。因此,虽然C++语言允许一个类型继承自多个基类型(多继承),但CTS既不能接受、也不能操作这样的类型。为了帮助开发人员,Microsoft的C++/CLI编译器一旦检测到托管代码中含有从多个基类型派生的一个类型,就会报错。
下面是另一条CTS规则:所有类型最终必须从预定义的System.Object类型继承。可以看出,Object是System命名空间中定义的一个类型的名称。Object是其他所有类型的根,因为保证了每个类型实例都有一组最基本的行为。具体地说,System.Object类型允许做下面这些事情:
- 比较两个实例的相等性(Equals)
- 获取实例的哈希码(GetHashCode)
- 查询一个实例的真正类型(GetType)
- 执行实例的(浅)拷贝(MemberwiseClone)
- 获取实例对象的当前状态的一个字符串表示(ToString)