[.NET大牛之路 008] 理解 CTS 和 CLS

今天我们来学习两个概念 CTS(Common Type System,通用类型系统)和 CLS(Common Language Specification,通用语言规范),以及了解一下它们的作用。

01 CTS 通用类型系统

一个程序集可能包含任意数量的不同类型。在 .NET 的世界里,类型只是一个一般的术语,用来指代类、接口、结构、枚举或委托(共五种)。当你使用 .NET 平台的语言进行编程时,你很可能会与许多类型进行交互。例如,你的程序集可能定义了某个类,实现了一些接口,可能其中一个方法需要一个枚举类型作为参数,并返回一个结构类型给调用者。

CTS(Common Type System,通用类型系统)是一个正式的规范,它记录了必须如何定义类型才能被 .NET 公共语言运行时(CLR)托管。

通常情况下,只有那些针对 .NET 平台开发工具或编译器的人,才会对 CTS 的内部工作原理感兴趣。对于几乎所有的 .NET 程序员来说,不需要对它进行深入研究。但对于 CTS 定义的五种类型,我们必须知道怎么使用你喜欢的语言(C#、VB 或 F#) 来定义它们。

以下基于 C# 简单的概述一下 CTS 的五种类型如何使用。

CTS 类类型

每一种 .NET 语言都至少支持类类型的概念,这是面向对象编程(OOP)的基石。一个类可以由任何数量的成员(如构造函数、属性、方法和事件)和数据字段组成。在 C# 中,类是用 class 关键字来声明的,像这样:

class Calc
{
  public int Add(int num1, int num2)
  {
    return num1 + num2;
  }
}

下面是 CTS 类类型的主要特征:

  • 是否密封(Sealed):密封类不能被其它类型继承,即它不能作为其它类的基类。
  • 是否实现接口:接口是一个抽象成员的集合,它在对象和对象的调用者之间提供了一种契约。CTS 允许一个类实现任何数量的接口。
  • 是抽象(Abstract)还是具体(Concrete):抽象类不能被直接实例化,其目的是为派生类型定义通用行为。具体类可以直接被实例化。
  • 可见性(Visibility):每个类都必须有一个可见性关键字,如 public 或 internal 等。它控制着该类的可访问性,例如 internal 表示不可以被外部程序集使用。

CTS 接口类型

接口是抽象成员的定义的集合(在 C# 8 中还新增了默认实现)。在 C# 中,接口类型是用 interface 关键字定义的。按照约定,所有的 .NET 接口都以大写字母 I 开头,如下面的例子:

public interface IDraw
{
  void Draw();
}

就接口本身而言,接口没有什么用处。但,当多个类或结构以特定的方式实现了一个指定的接口时,你就能够以多态的方式使用接口提供的功能。

CTS 结构类型

简单来说,结构可以认为是一种具有基于值的轻量级类类型。通常情况下,结构最适合用于几何和数学数据的建模。在 C# 中使用 struct 关键字创建一个结构类型,如下所示:

struct Point
{
  // 结构类型的字段
  public int X, Y;

  // 结构类型的带参构造函数
  public Point(int x, int y)
  { X = x; Y = y;}

  // 结构类型的方法
  public void PrintPosition()
  {
    Console.WriteLine("({0}, {1})", X, Y);
  }
}

CTS 枚举类型

枚举是一种很方便的结构,它允许你将键-值对分组。例如,假设你正在开发一款游戏,则可以用枚举定义玩家的角色(如魔法师、战士和盗贼等)。C# 中使用 enum 关键字创建一个强类型的枚举,而不是用简单的数值来表示每个项,例如:

enum CharacterType
{
  Wizard = 100,
  Fighter = 200,
  Thief = 300
}

默认情况下,枚举项的存储单位是一个 32 位的整型(System.Int32),也可以改为其它的(如Sytem.Byte)。另外,所有 CTS 枚举类型都派生自一个共同的基类–System.Enum

CTS 委托类型

.NET 的委托类型类似于 C 语言的函数指针。关键的区别在于,.NET 的委托是一个继承自 System.MulticastDelegate 的类,而不是一个指向原始内存地址的简单指针。在 C# 中,委托是用 delegate 关键字声明的:

delegate int Calc(int x, int y);

委托的作用在于:为一个对象提供一种方法来转发对另一个对象的调用,即它可以把对象的函数当作参数传递。它是 .NET 事件、多播(即把一个请求转发给多个接收者)和异步方法调用(即在一个线程上调用另一个线程的方法)的基础。

CTS 类型的成员

CTS 的大多类型都可以有任何数量的成员。从形式上讲,类型的成员的各类有:构造函数、终结器、静态构造函数、嵌套类型、运算符、方法、属性、索引器、字段、只读字段、常量和事件。

CTS 定义了可能与给定成员相关联的各种修饰符。例如,每个成员都有一个给定的可见性特征(例如,publicprivateprotected等)。一些成员可以被声明为抽象的(在派生类型上强制执行多态行为)以及虚的(定义一个封装好的、可被重写的实现)。另外,大多数成员可以被配置为静态的(绑定在类的层面)或实例的(绑定在对象的层面)。

CTS 基本数据类型

CTS 建立了一套定义明确的基本数据类型。尽管不同的语言通常用不同的关键字声明基本数据类型,但所有的 .NET 平台的语言关键字最终都会被解析为相同的 CTS 类型,并定义在一个名为 mscorlib.dll 的程序集中。下表是 CTS 基本数据类型分别在 VB.NET 和 C# 中的表达:

[.NET大牛之路 008] 理解 CTS 和 CLS

语言特有的关键字只是 System 命名空间中真实类型的速记符号,它们本质上没有区别。考虑以下代码片段,它们在 C# 和 VB 中定义了 32 位整型变量,使用了语言关键字以及 CTS 数据类型:

// 在 C# 中定义 32 位整型
int i = 0;
System.Int32 j = 0;
// 在 VB 中定义 32 位整型
Dim i As Integer = 0
Dim j As System.Int32 = 0

02 CLS 通用语言规范

正如上面所演示的,不同的语言用自己独特的语法来表达相同的意思。例如,在 C# 中,用运算符 + 表示字符串连接,而在 VB 中通常用 & 符号表示。即使两种不同的语言表达了相同的意思(例如,一个没有返回值的函数),它们的语法可能是完全不同的,例如:

// 没有返回值的 C# 方法
public void MyMethod()
{
  // Do something...
}
' 没有返回值的 VB 方法
Public Sub MyMethod()
  ' Do something...
End Sub

在 .NET 运行时眼中,这些语法的不同是不被感知的,因为各自的编译器(C# 是 csc.exe,VB 是 vbc.exe)会生成一组类似的 IL 代码。然而,语言也可以在其整体功能水平方面有所不同。例如,.NET 的语言中,有的可能支持无符号数值的关键字,有的可能不支持;有的可能支持支持指针类型,有的可能不支持。鉴于这些语言功能水平的不同,但又为了能够实现各个语言这间的互操作,所以需要划定一个基准来规定所有的 .NET 语言都要符合一定的规范。在这个规范内,各种 .NET 语言都可以互操作。

这样的一个规范就是 CLS(Common Language Specification,通用语言规范)。

CLS 是一套详细地描述了 .NET 编译器必须支持的最小功能集,以生成可由 .NET 运行时托管的代码,同时可由所有 .NET 平台的语言以统一方式访问。CLS 可以被视为 CTS 所定义的全部功能的一个子集。

CLS 规则示例

CLS 最终是一套规则,如果编译器开发者想让他们的产品在 .NET 平台无缝运行,就必须遵守这套规则。每条规则都有一个简单的名称(例如,CLS Rule 6),并描述了如何规范编译器的行为。CLS 的精华部分是 Rule 1:

Rule 1:CLS 规则只适用于一个类型中暴露在定义程序集之外的部分。

鉴于这条规则,你可以推断出 CLS 的其余规则并不适用于 .NET 类型的内部工作逻辑。类型中唯一必须符合 CLS 的是成员定义本身(即,命名约定、参数和返回类型)。成员的实现逻辑可以使用任意的非 CLS 技术,因为外部世界不会知道成员内部的细节。

这样说可能有点抽象,还是来举个例子吧。

下面这个 C# 的 Add() 方法不符合 CLS,因为参数和返回值使用了无符号数值(这不是 CLS 的要求):

class Calc
{
  // 向外暴露无符号数值不遵从 CLS 规则
  public ulong Add(ulong num1, ulong num2)
  {
    return num1 + num2;
  }
}

然而,请考虑以下代码,它在一个方法内部使用了无符号数值:

class Calc
{
  public int Add(int num1, int num2)
  {
    // 由于 ulong 类型的变量只在方法内部使用,
    // 它仍然遵从 CLS 规则
    ulong temp = 0;
    ...
    return num1 + num2;
  }
}

该方法仍然符合 CLS 的规则,可以放心地让所有 .NET 语言都能调用 Add() 方法。

当然,除了 Rule 1 之外,CLS 还定义了许多其他规则。例如,CLS 描述了特定语言必须如何表示文本字符串,枚举应该如何在内部表示(用于存储的基本类型),如何定义静态成员,等等。不过,你不必了解这些规则细则,实际开发中不会用到这些知识。

确保符合 CLS 规范

正如上面演示的,C# 确实定义了一些不符合 CLS 的编程结构。那怎么来保证你的 C# 代码符合 CLS 规范呢?你可以使用一个 .NET 特性来告诉 C# 编译器来检查你的代码是否符合 CLS 规范:

// 告诉 C# 编译器检查是否符合 CLS 规范
[assembly: CLSCompliant(true)]

这里的 [CLSCompliant] 属性将告诉 C# 编译器根据 CLS 的规则检查代码。如果发现违反 CLS 规则的行为,会收到一个编译器的警告和对违规代码的描述。

03 小结

本文探讨了 .NET 中的 CTS(通用类型系统)和 CLS(通用语言规范)。我们需要知道什么是 CTS 和 CLS,以及它们的作用,其它的细节不重要,了解一下即可。

本文来自http://cnblogs.com/willick,经授权后发布,本文观点不代表Chaoqiang's Blog立场,转载请联系原作者。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

近期个人博客正在迁移中,原博客请移步此处,抱歉!