首页 > 代码库 > CTS、CLS、CLR

CTS、CLS、CLR

CTS

  一个给定的程序集可能包含任意数量的不同“类型”。在.NET领域,类型(type)是一个一般性的术语,它指的是集合{类,接口,结构,枚举,委托}里的任意一个成员。当用支持.NET的语言构建解决方案时,很有可能要与这些类型打交道。例如,程序集可能定义了一个类,它又实现了一些接口。或许其中某个接口方法采用枚举类型作为输入参数,而在调用时返回一个结构。

  CTS(公共类型系统)是一个正式的规范,它规定了类型必须如何定义才能被CLR承载。通常,只有那些创建针对.NET平台的工具或编译器的人才对CTS的内部工作非常关心。但是,对于所有.NET编程人员来说,学习如何在自己使用的语言中使用由CTS定义的5种类型,是非常重要的。这里简单概括一下。

CTS类类型

  每一种支持.NET的语言至少要支持类类型(class type)的概念,这是OOP的基础。类可能由很多成员(诸如构造函数、属性、方法和事件)和数据点(字段)组成。在C#中,使用class关键字来声明类。

  表1-1给出了有关类类型的一些特征。

表1-1 CTS类类型

类的特征 在生命周期里的意义
类是否被“密封” 密封类不能作为其他类的基类
类实现任何接口了吗 接口是抽象成员的集合,它在对象和对象的用户间提供一个契约。CTS允许类实现任何数目的接口
类是具体的还是抽象的 抽象类是不能直接创建的,但是可以用来为派生类型定义公共的行为。具体类可以直接创建
这个类的可见性是什么         每个类必须用关键字(public或internal)设置可见性。基本上,可见性定义了该类是被外部程序集使用,还是仅能在定义了它的程序集中使用

CTS接口类型

  接口(interface)就是由抽象成员定义所组成的一个具名集合,可通过一个给定的类或结构来实现。在C#中,接口类型使用interface关键字来定义。一般情况下,所有的.NET接口均以大写字母I开头。

  就它们自身而言,接口没有什么用。然而,当一个类或结构用其独特方式来实现一个给定接口时,你将能够以多态方式通过接口引用来请求使用所提供的功能。

CTS结构类型

  CTS中还支持结构(structure)的概念。简单地说,结构(struct)可以看做是具有值语义的轻量级类类型。通常,结构最适合建模几何和数学数据。在C#中,通常使用struct关键字创建结构。

CTS枚举类型

  枚举(enumeration)是一种便利的编程结构,它可以用来组成名称/值对。例如,假设你在开发一个视频游戏的程序,要让玩家在3种角色(Wizard、Fighter或Thief)中选择一个。你完全可以用enum关键字来建立一个自定义的枚举,而不用老是要记着代表每种可能性的原始数字值。

  在默认情况下,每一项是用一个32位的整数来存储的,但如果需要,也可以改变存储大小(例如,在为Windows移动设备之类的低内存设备编程时)。另外,CTS要求枚举类型派生自基类System.Enum。这个基类定义了一些有趣的成员,允许通过编程提取、操作和变换底层的名称/值对。

CTS委托类型

  委托(delegate)在.NET中等效于类型安全的C风格的函数指针。它们的主要不同在于,.NET委托是派生自System.MulticastDelegate的类,而不是一个简单地指向原始内存地址的指针。在C#中,委托是使用关键字delegate来声明的。

  一个实体可以用委托向另一个实体传递调用,另外,委托也为.NET事件架构提供了基础。委托对多路广播(即将一个请求转发给多个接收者)和异步方法调用(即从另一个线程调用方法)有着内在支持。

CTS类型成员

  现在你已经看到了由CTS正式规定的各种类型,但还要认识到,大部分的类型可以含有任意数量的成员。说得更正式一些,类型成员是集合{构造函数,终结器,静态构造函数,嵌套类型,操作符,方法,属性,索引器,字段,只读字段,常量,事件}中的元素之一。

  CTS定义了各种可能与具体成员关联的修饰语(adomment)。例如,每个成员都有一个给定的可见性特征(如公共的、私有的和受保护的等)。有些成员可能被声明成抽象的以加强派生类的多态性,有些成员可声明为虚拟的以定义一个封装(但可重写)的实现。同样,绝大部分成员可设置成静态的(在类级别绑定)或者实例(在对象级别绑定)。

內建的CTS数据类型

  CTS需要关注的最后一个方面是,它建立的一套定义明确的核心数据类型。尽管不同的语言通常都有自己唯一的用于声明內建CTS数据类型的关键字,但是所有语言的关键字最终将解析成定义在mscorlib.dll程序集中的相同类型。参考表1-2,它描述了如何在不同的.NET语言中表示关键的CTS数据类型。

表1-2 內建的CTS数据类型

CTS数据类型 VB关键字 C#关键字 C++/CLI关键字
System.Byte Byte byte unsigned char
System.SBtye SByte sbyte signed char
System.Int16 Short short short
System.Int32 Integer int int 或 long
System.Int64 Long long _int64
System.UInt16 UShort ushort unsigned short
System.UInt32 UInteger uint  unsigned int 或 unsigned long
System.UInt64 ULong ulong unsigned _int64
System.Single Single float Float
System.Double Double double double
System.Object Object object object^
System.Char Char char wchar_t
System.String String string String^
System.Decimal Decimal decimal Decimal
System.Boolean Boolean bool bool

  由于各种委托语言的关键字只是System命名空间中真实类型的简化符号,我们不需要担心数值数据的上溢或下溢,或是字符串和布尔型数据在内部是怎样跨不同语言进行表示的。下面的代码片段使用C#和VB,通过语言关键字和正式的CTS数据类型分别定义了32位数值变量。

// 用C#定义整型数据
int i = 0;
System.Int32 j = 0;
 用VB定义整型数据
Dim i As Integer = 0
Dim j As System.Int32 = 0

 

CLS

  不同的语言往往用不同的、语言特定的术语表达相同的程序构造,比如,在C#中使用加号(+)操作符表示字符串拼接,而在VB中却使用“&”符号。即使两种不同的语言表达相同的编程惯用法(比如一个不返回值的函数),在表面看起来,语法也可能非常不同。

  在.NET运行库看来这些较小的语法变化是微不足道的,因而不同的编译器(这里用到的是vbc.exe或csc.exe)将产生类似的CIL指令集。然而,语言也可能在功能上不同,比如,.NET语言可能有也可能没有关键字来表示无符号数据,可能支持也可能不支持指针类型。对于这些可能的变化,理想情况是所有支持.NET的语言都有一个可以遵循的基准。

  CLS就是这样一套规则,它清晰地描述了支持.NET的编译器必须支持的最小的和完全的特征集,以生成可由CLR承载的代码,同时可以被基于.NET平台的其他语言用统一的方式进行访问。CLS可以看成是由CTS定义的完整功能的一个子集。

  如果打算让自己的产品功能无缝地融合到.NET世界,那么CLS是编译器创建者最终必须要遵循的一套规则。每个规则被赋予一个简单的名字(如CLS规则6),描述了这个规则如何影响创建编译器的人以及(以某种方式)与他们交互的人。影响最大的是规则1。

  • 规则1:CLS规则仅适用于类型中向定义它的程序集以外公开的部分。

  根据这个规则,可以(正确地)推断其余的CLS规则对于用来建立一个.NET类型内部运行功能的逻辑是不适用的。必须遵循CLS的类型的唯一一点,就是成员定义本身(即命名规范、参数和返回类型)。成员的实现逻辑可以使用其他的非CLS技术,程序外部并不知道这些不同。

  举例说明,下面的Add()方法就没有遵循CLS规则,因为它的参数和返回值使用了无符号数(无符号数不符合CLS):

class Calc
{
    //公开的无符号类型数据不遵循CLS规则
    public ulong Add(ulong x, ulong y)
    { return x + y; }
}

  然而,如果像下面一样在程序内部使用无符号数:

class Calc
{
    public int Add(int x, int y)
    {
        // 当ulong类型变量仅仅在内部使用时,仍然遵循CLS规则
        ulong temp = 0;
        ...
        return x + y;
    }
}

  这仍然遵循CLS规则,可以保证所有的.NET语言都能调用Add()方法。

  当然,除规则1外,CLS还定义了很多其他的规则。例如,CLS描述了一种语言如何表示文本字符串,如何在内部表示枚举(用于存储的基类型),如何定义静态成员,等等。好在你不需要记忆所有的规则也能成为静态.NET的程序员。总地来说,只有那些工具/编译器的开发人员才会对CTS和CLS规范的具体细节感兴趣。

确保遵循CLS

  C#定义了一些不遵循CLS规则的程序结构,但你仍然可以使用一个专门的.NET特性指示C#编译器检查代码是否遵循CLS规则。

// 指示C#编译器检查是否遵循CLS规则
[assembly:CLSCompliant(true)]

  目前,只需要知道[CLSCompliant]特性就是用来指示C#编译器按CLS规则检查每行代码的。如果代码违反了CLS,就会给出编译器错误和关于错误代码的描述。

 

CLR

  从编程角度来说,运行库(runtime)可以理解为执行给定编译代码单元所需的外部服务的集合。比如,当Java程序员向一台新电脑部署软件时,要确保软件运行,电脑上就要安装JVM(Java Virtual Machine,Java虚拟机)。

  .NET平台提供了另一种运行库系统。.NET运行库与刚才提到的其他运行库的关键不同在于,.NET运行库提供了一个定义明确的运行库层,可以被支持.NET的所有语言和平台所共享。

  CLR中最重要的部分是由名为mscoree.dll的库(又称公共对象运行库执行引擎)物理表示的。当用户程序引用一个程序集,要使用它时,mscoree.dll将首先自动加载,然后由它负责将需要的程序集导入内存。运行时引擎负责许多任务,首要的任务是负责解析程序集的位置,并通过读取其中包含的元数据,在二进制文件中发现所请求的类型。接着,CLR在内存中为类型布局,将关联的CIL编译成特定平台的指令,执行所有需要的安全检查,然后运行当前的代码。

  除了导入自定义的程序集和建立自定义的类型,必要时CLR也会与包含在.NET基础类库的类型交互。虽然完整的基础类库被分为若干分离的程序集,但最重要的程序集是mscorlib.dll。mscorlib.dll包含大量核心类型,它们封装了各种常见的编程任务与.NET语言用到的核心数据类型。当建立一个.NET解决方案时,你可以自动访问这些程序集。

  图1-4说明了发生在源代码(它是用来许多基础类库类型)、.NET编译器和.NET执行引擎之间的工作流。

技术分享

图1-1 mscoree.dll工作流

 

CTS、CLS、CLR