本篇内容大纲
一、Framework 类库(Framework Class Library , FCL)
1、mscorlib.dll (Microsoft Core Library,微软核心类库)
我们先来看个例子。新建一个控制台程序,然后在Main方法中输入如下代码:
1 static void Main(string[] args) 2 { 3 string str = "Hello World"; 4 Console.WriteLine(str); 5 }
我们将光标放在 Console类型上,按F12转到定义,会跳转到:
#region 程序集 mscorlib.dll, v4.0.30319 // C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll #endregion using System.IO; using System.Runtime.ConstrainedExecution; using System.Security; using System.Text; namespace System { // 摘要: // 表示控制台应用程序的标准输入流、输出流和错误流。此类不能被继承。 public static class Console { // 略... } }
从上面代码可以得知Console类型定义在mscorlib.dll中,然后我们再看我们的项目的引用:
我们的项目中并没有添加对mscorlib.dll的引用,甚至我们将所有的引用都删除,上面的代码还是可以正常运行。由此可以看出,不管我们是否引用mscorlib.dll程序集,它总是会自动引用进来。这个程序集中所包含的类库,就是微软核心类库(Mricrosoft Core Library),有些资料也称之为BCL (Base Class Library,基类库)。从名字就可以看出来,这个类库包含的都是些最基本的核心类型,其本身已经与IL语言融为一体了,为IL语言提供基础的编程支持,以至于该类库已经成为了LI标准的一部分(因此也可以说mscorlib.dll中的类型就是IL语言的类型,所有面向IL的语言都能够使用它们)。我们可以使用对象浏览器(Visual Studio菜单→视图→对象浏览器)来查看mscorlib.dll程序集中都包含了哪些命名空间和类型,如下图:
mscorlib.dll程序集:
可以看到该程序集下包含的主要是System命名空间,稍微细心一点的读者会发现,在新建项目的时候,还包含了System.dll程序集,并且其中所包含的类型与mscorlib中的类型十分相似。
System.dll程序集:
这又是怎么回事呢?实际上,只要点开System命名空间就会发现,mscorlib.dll的System命名空间下面定义的类型和System.dll的System命名空间下面定义的类型完全不同,它们之间并没有冲突之处。如下图:
mscorlib.dll 的 System命名空间下的类型 | System.dll 的 System命名空间下的类型 |
---|---|
从上面的几幅图我们就明白了:mscorlib.dll提供了像Console这样的类型来支持开发者编写类似控制台这样的程序。这也就解释了,为什么我们删除了项目的所有引用,而我们的
Console.WritLine(str);还是可以正常运行,其实头部的 这些个引用都是来自mscorlib.dll(这没了解这部分之前,小白的我一直以为这些using的命名空间是存在于System.dll中的。这段关于mscorlib.dll的介绍引自-子阳的博文)。
我们再来看下string text = “hello, world !”,其中的string从哪里来?从直觉来看,string在Visual Studio中以深蓝色呈现,属于C#的关键字,那么它应该是C#提供的内置类型。可是,当我们将光标移动到string上并按下F12时,转到string的定义时,看到的却是下面这样的内容:
#region 程序集 mscorlib.dll, v2.0.50727 // C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorlib.dll #endregion using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Text; namespace System { // 摘要: // 表示文本,即一系列 Unicode 字符。 [Serializable] [ComVisible(true)] public sealed class String : IComparable, ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, IEnumerable, IEquatable<string> { //略。。。 } }
注意最上方的程序集地址,再次看到了mscorlib.dll,并且String类型与Console类型一样,同位于System命名空间下。由此可见,C#的关键字string,不过是BCL中System.String类型的一个别名而已。类似地,VB.NET中的String关键字也是BCL中的System.String类型的别名。因此,在.NET框架中,语言从本质上来说没有太大的区别,更多的区别是在语法方面。从上面的例子也可以看出,C#和VB.NET的很多语言能力并不是自己的,而是从IL“借”过来的这样做也保证了在不同语言中相应类型的行为是一致的。下表列出了几个典型的,不同语言关键字与IL类型的对应关系。
不同语言关键字与CIL类型的对应关系
CIL 类型 | C# 关键字 | VB.NET关键字 |
System.Byte | byte | Byte |
Sytem.Int16 | short | Short |
System.Int64 | long | Long |
笔者觉得理解重于记忆,所以这里只列出了几个。要了解其他基础类型时,只要将光标移动到类型上,然后再按下F12键就可以了。大家可能听说过这样一种特殊的类型——基元类型(Primitive Type)。实际上,讲到这里大家应该已经明白了,那些由编译器直接支持,将语言本身的关键字类型转换为IL类型的,就叫做基元类型。显然,上面的byte、int、string都是基元类型。而C#中并没有一个关键字去映射Console,所以我们认为Console只是普通的类类型(Class Type)。
2、Framework Class Library,FCL(框架类库)
.NET Framework 中包含了Framework 类库(Framewokr Class Library, 框架类库)。FCL 是一组DLL程序集的统称(如上面介绍的 mscorlib.dll-Microsoft Core Library 就是FCL的一个子集),其中包含有数千个类型定义,每个类型都公开了一些功能。从功能上来看,可以将FCL框架类库划分成以下几层。
二、通用类型系统(Common Type System,CTS)
经过上面一节的了解,我们知道.NET Framework 包含了Framework Class Library(FCL) ,而FCL是一组DLL程序集的统称,这些个程序集为我们程序开发提供了很多功能,我们也可以扩展这些程序集和定义自己的程序集,而这些程序集的运行都是由CLR来管控的。所以,CLR 是完全围绕“类型”展开的(笔者认为:这里的“类型”不是指FCL中的类型,也不是指我们自定义的类型,而是一种更高层次的“类型”)。这里有点难以理解,请看下面的例子:
我们自定义个类型 Person
public class Person
{
// 省略实现
}
//实例化 Person 类型
Person p1 = new Person();
Person p2 = new Person();
对于以上代码,通常是这么描述的:定义了一个Person类,并且创建了两个Person类的实例p1、p2。实际上这只包含了两层含义如下表所示。
类、类的实例
类 | Person |
类的实例 | p1,p2 |
再思考一下就会发现,还有一个更高的层面,那就是Person这个类的类型,我们称之为类类型(Class Type),因此上表可以改成:
类类型、类、类的实例
类类型 | class |
类 | Person |
类的实例 | p1,p2 |
类似的,还有枚举类型(Enum Type)、结构类型((Struct Type)等。现在大家应该明白这里要表达的意思了,CLR 是围绕这些更高层次的“类型”展开的,而通过“类型”,用一种编程语言写的代码能与另一种语言写的代码沟通。由于类型是CLR的根本,所以Microsoft 制定了一个规范这些高层次”类型“的定义、规则或标准,即“通用类型系统”(Common Type System,CTS),任何满足了CTS规则的高级语言就可以称为面向.NET框架的语言。C#和VB.NET不过是微软自己开发的一套符合了CTS的语言,实际上还有很多的组织或团体,也开发出了这样的语言,比如Delphi.Net、FORTRAN等。
CTS规定了可以在语言中定义诸如类、结构、委托等类型,这些规则定义了语言中更高层次的内容。因此,在C#这个具体的语言实现中,我们才可以去定义类类型(Class Type)或者结构类型(Struct Type)等。
同样,可以在Person类中定义一个字段name并提供一个方法ShowName()。实际上,这些也是CTS定义的,它规范了类型中可以包含字段(filed)、属性(property)、方法(method)、事件(event)等。
除了定义各种类型外,CTS还规定了各种访问性,比如Private、Public、Family(C#中为Protected)、Assembly(C#中为internal)、Family and assembly(C#中没有提供实现)、Family or assembly(C#中为protected internal)。
CTS还定义了一些约束,例如,所有类型都隐式地继承自System.Object类型,所有类型都只能继承自一个基类。从CTS的名称和公共类型系统可以看出,不仅C#语言要满足这些约束,所有面向.NET的语言都需要满足这些约束。众所周知,传统C++是可以继承自多个基类的。为了让熟悉C++语言的开发者也能在.NET框架上开发应用程序,微软推出了面向.NET的C++/CLI语言(也叫托管C++),它就是符合CTS的C++改版语言,为了满足CTS规范,它被限制为了只能继承自一个基类。
关于上面内容有两点需要特别说明:
1)C#并没有提供Family and assembly的实现,C#中也没有全局方法(Global Method)。换言之,C#只实现了CTS 的一部分功能。,也就是说,CTS规范了语言能够实现的所有能力,但是符合CTS规范的具体语言实现不一定要实现CTS规范所定义的全部功能。
2)C++/CLI又被约束为只能继承自一个基类,换言之,C++中的部分功能被删除了。就是说,任何语言要符合CTS,其中与CTS不兼容的部分功能都要被舍弃。
显然,由于IL是.NET运行时所能理解的语言,因此它实现了CTS的全部功能。虽然它是一种低级语言,但是实际上,它所具有的功能更加完整。C#语言和IL、CTS、CLR的关系,可以用下图表示:
三、公共语言规范(Common Language Specification,CLS)
1、CLS的概念
COM 允许使用不同语言创建的对象相互通信。现在,CLR集成了所有语言,允许在一种语言中使用另一种语言创建的对象。之所以能实现这样的集成,是以为CLR使用了标准类型集、元数据(自描述的类型信息)以及公共执行环境。
要创建能让其他编程语言访问的支持的类型,只能从自己的编程语言中挑选其他所有语言都确定支持的那些功能。为了在这个方面提供帮助,Microsoft 定义了一个”公共语言规范"(Common Language Specification,CLS),它详细定义了一个最小的功能集。任何编译器生成的类型想要兼容于其他”符合CLS、CTS,面向CLR的语言"所生成的组件,就必须支持这个最小的功能集。CLR/CTS 支持的功能比CLS定义的子集要多的多。CLR/CTS、CLS与具体语言实现的关系如下图1所示:
图一 ClR/CTS , CLS 与具体语言的关系 | 图二 未实现CLS规范的语言与其他符合CLS规范的语言关系 |
CLS是各种语言中定义的类型相互兼容的前提。每种语言都提供了CLR/CTS的一个子集以及CLS的一个超集(但不一定是同一个超集)。上图1描述:CLR/CTS 提供了一个功能集合。有的语言实现了CLR/CTS的一个较大的子集。例如,假定开发人员使用IL汇编语言写程序,那他就可以使用 CLR/CTS提供的所有功能。但是,其他大多数语言(比如 C#、F#、VB等)都只实现了CLR/CTS的一部分功能。CLS定义了所有语言都必须支持的一个最小的功能集合(如果各语言要相互兼容)。
具体来说,在开发类型和方法的时候,如果希望他们对外“可见”,能够从符合CLS的任何一种编程语言中访问,就必须遵守由CLS定义的规则。这里要注意的是:假如代码只是从定义(这些代码的)的程序集内部访问,CLS规则就不适用了。也就是说,我们在一种语言中定义的类型,希望在另一种符合CLS的语言中使用时,我们必须将定义的类型设置为public或者protected,并且不要在类型中使用位于CLS之外的功能。否则其他语言就不能访问我们定义的类型了,当然,假如某一种语言N#,它创建的类型和方法,不想被其他语言访问,那这个语言可以不用实现CLS的规范,这时这种语言和其他语言及CLR/CTS的关系如上图2。
2、Sample 理解CLS
下面我们来举个例子具体说明CLS:现在我们知道了 CLS是各种面向CLR/CTS的语言能相互操作必须满足的语言规范,而面向CLR的语言需要符合CTS的规范,加入我们现在开发一套面向CTS的语言,这种语言叫 N#,它实现的功能非常有限,它值只实现了CLS/CTS的非常小一部分功能,它和CLR/CTS、C#等其他语言的关系可能如下图3所示:
图三 自定义的面向CLR的语言N# | 图四 自定义的面向CLR的语言N# |
好,现在N#语言是面向CLR并且满足CTS规范,那我们就可以在.NET 平台下使用 N#提供的功能来开发了。但是现在有个问题,N#创建的类型和方法,C#语言能访问吗?从上图可以看出,显然是不能访问的。因为,虽然C#和N#都是面向CLR也符合CTS通用类型规则,但是它们并没有交集合。如果要N#和其他语言能相互兼容,那N#也必须符合CLS规范,N#和其他语言的关系就变成上图4所描述的那样了。至此,我们应该明白 CLS是个什么概念了。
3、检测定义的类型、方法是否符合CLS规范
我们如何去检测我们创建的类型和方法是否符合CLS的规范呢?.NET为我们提供了一个特性CLSCompliant,便于在编译时检查我们定义的类型或方法是否符合CLS。我们来看下面一个例子:
using System; [assembly: CLSCompliant(true)] //因为是public 类,所以会显示警告,如果是私有的,则不检测CLS规范 public class ValidateCLSDemo { public string name; //警告1:仅大小写不同的标识符 ValidateCLSDemo.Name()不符合CLS public string Name() { return this.name; } //警告2:ValidateCLSDemo.GetInt() 的返回值类型不符合CLS public UInt32 GetInt() { return 0; } //私有方法,不显示警告 private UInt32 getInt() { return 0; } }
上述代码将[assembly:CLSCompliant(true)]应用与程序集,这个特性告诉编译器检查public的类型和方法,判断是否存在不符合CLS规范的内容,阻止了从其他编程语言中访问该类型。上述代码会显示两条警告,第二个警告是public的方法GetInt()返回了一个无符号的整数,有一些语言是不能操作无符号的整数的。第一个警告是存在两个public 的名为name的成员,只是大小写不同。VB和其他的语言无法区分这个两个成员的。这时,如果我们将ValidateCLSDemo类的public 去掉,再编译所有的警告都消失了。因为去掉了public 类就成默认的internal访问级别了,不再对程序集外部公开。要获得完整的CLS规则列表,可以参见:msdn-跨语言互操作性。
在CLR中类型的每个成员要么是字段,要么就是方法。这意味着每一种面向CLR的语言都能访问字段和调用方法。这些字段和方法通过特殊或者通用的方式来访问和调用。为了方便进行编程,语言通常提供了额外的抽象,对这些常见的编程模式进行了简化。例如:语言可能公开枚举、数组、属性、索引器、委托、事件、构造器、析构器、操作符重载、转换操作符等概念。编译器在源代码中遇到上述任何一种构造,都必须将其转换成 字段和方法,使CLR和其他语言能够访问这些结构。也就是说编译器最终编译成的IL代码中,只包含字段和方法,上面列出的具体语言提供的枚举、属性、等等结构都会被转换成字段和方法。可以使用 ILDasm.exe IL反编译工具来查看最终编译的托管模块中的IL代码。
关于IL代码的更多资料可以参见 赵姐夫的博客:老赵谈IL.
四、总结
到这里,《第一章:CLR的执行模型》已看完了。读完这两篇,我们只要明白以下几个问题:
1、什么是托管模块?托管模块中的包含哪些内容?
2、什么是程序集?程序集中包含那些内容,它和托管模块的关系?
3、程序集的执行过程-CLR JitCompiler对代码的编译过程?
4、.NET Framwork 框架 包含那些东西?核心是什么?
5、CLR、CTS、CLS、FCL都是什么东西?以及他们之间的包含关系?
以上问题也是面试中常见的问题,我希望看完这两遍博客的园友能在自己心里问问自己,我明白了这几个问题吗?
申明:本篇部分内容借鉴 张子阳的博文:.NET 架构
如发现文中有误,或有更好的建议,欢迎指出!
作者:邹毅