ASP.NET Core 3 框架揭秘(上下册)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.1 历史的枷锁

对于计算机从业人员来说,“平台”(Platform)是一个司空见惯的词语。在不同的语境中,平台具有不同的语义,它可以指代操作系统环境和 CPU指令类型,也可以表示硬件设备类型。目前,微软已经为 Windows 平台构建了一个完整的且支持多种设备的.NET 生态系统。与此同时,通过借助 Mono 和 Xamarin,.NET 已经可以被成功移植到包括 macOS、Linux、iOS、Android和FreeBSD等非Windows平台。

2.1.1 Windows下的.NET

微软于 2002 年推出的第一个版本的.NET Framework 是一个主要面向 Windows 桌面(Windows Forms)和服务器(ASP.NET Web Forms)的基础框架。在此之后,计算机的地位不断受到其他设备的挑战,为此微软根据设备自身的需求对.NET Framework做了相应的简化和修改,不断推出针对不同设备类型的.NET Framework,如 Windows Phone、Windows Store、Silverlight和.NET Micro Framework等,它们分别对移动设备、平板设备和嵌入式设备提供支持。由于这些不同的.NET Framework分支是完全独立的,所以开发一款同时支持多种设备的“可移植”(Portable)应用的难度很大。

.NET Framework的层次结构

由于不同设备.NET Framework的独立性,所以需要编写多种针对不同设备平台的代码,跨设备平台的代码重用显得异常困难。为了使读者深刻理解这个问题,本节从.NET Framework的层次结构开始介绍。.NET Framework由图2-1所示的两个层次构成,它们分别是提供运行环境的CLR(Common Language Runtime)和提供API的FCL(Framework Class Library)。

图2-1.NET Framework=Runtime+FCL

CLR与.NET的关系等同于JVM与Java的关系,CLR本质上就是.NET虚拟机。作为一个运行时(Runtime),CLR 为程序的执行提供一个托管(Managed)的执行环境,它是.NET Framework 的执行引擎,为托管程序的执行提供内存分配、垃圾回收、安全控制、异常处理和多线程管理等方面的服务。CLR是.NET Framework的子集,但是两者具有不同的版本策略。截至 2019 年,微软仅发布了 4 个版本的 CLR,分别是 CLR 1.0、CLR 1.1、CLR 2.0 和CLR 4.0,.NET Framework 1.0和.NET Framework 1.1分别采用 CLR 1.0与 CLR 1.1,CLR 2.0被.NET Framework 2.0和.NET Framework 3.x共享,.NET Framework 4.x下的运行时均为CLR 4.0。

FCL是一个旨在为开发人员提供 API的类库,由它提供的 API又可以划分为两个层次(见图2-1)。处于最底层的部分被称为BCL(Basic Class Library),用于描述一些基本的数据类型和数据结构(如字符串、数字、日期/时间和集合等),同时提供一些基础性的操作(如 IO、诊断、反射、文本编码、安全控制、多线程管理等)。在 BCL 之上则是面向具体应用类型的 API,大体上可以将它们划分为以下3种类型。

面向应用(如ASP.NET、WPF和Windows Forms等)。

面向服务(如WCF、WF和Data Services等)。

面向数据(如ADO.NET、Entity Framework和LINQ to SQL等)。

也可以采用另一种方式对 FCL 进行重新划分,即:将面向某种应用或者服务类型(如Windows Forms、WPF、ASP.NET 和 WCF 等)的部分称为 AppModel,那么整个.NET Framework就具有三层结构(见图2-2)。

图2-2.NET Framework=Runtime+BCL+AppModel

大而全的BCL

微软的.NET战略是在2000年提出的,2002年.NET Framework 1.0和IDE(VS.NET 2002)随之问世。在之后的10多年中,.NET Framework的一系列版本被先后推出。截至2019年,微软发布的最新.NET Framework版本为4.7,图2-3展示了整个.NET Framework不断升级的演进过程,以及各个版本提供的主要特性。

图2-3.NET Framework版本升级(功能点)

图 2-3 展示了.NET Framework 的发展历程,由此说明:作为整个.NET 平台的基础框架,.NET Framework在不断升级过程中虽然变得更加强大和完整,但是在另一方面也变得越来越臃肿。随着版本的不断升级,构成.NET Framework的AppModel、BCL和CLR都在不断膨胀(.NET Framework 2.0/3.x和.NET Framework 4.x分别采用CLR 2.0和CLR 4.0),图2-4很直观地说明了这个问题。

图2-4.NET Framework版本升级(自身尺寸)

程序集是.NET 最基本的部署单元,无论定义中的多少类型被使用,CLR 总是将整个程序集加载到内存中。对于上面介绍的构成.NET Framework 的三个层次来说,AppModel 是针对具体应用/服务类型的,相应的 API 通过独立的程序集来承载(如 ASP.NET 的核心框架定义在程序集System.Web.dll中,承载整个Windows Forms框架的程序集则是System.Windows.Forms.dll),所以.NET Framework的各个应用模型是相互独立的。在开发某种类型的应用时,引用应用模型对应的程序集即可。例如,如果开发的是一个 Windows Forms 应用,是不需要引用System.Web.dll程序集的。

将针对某种 AppModel 的 API 定义在单一程序中并不会带来很严重的问题,但是针对单程序集的 BCL承载方式就会不一样。因为 BCL绝大部分的核心代码都定义在 mscorlib.dll程序集中,所以 BCL 基本上是作为一个不可分割的整体存在于.NET Framework 之中的。由于.NET Framework 需要对运行在本机各种类型的托管程序提供支持,所以针对所有应用类型的基础类型均需要定义在 BCL 中。在很多情况下,我们的应用可能仅仅需要使用 BCL 一个很小的子集,但是需要将定义整个程序集都加载到内存之中。

一方面,BCL 总是作为一个不可分割的整体被加载;另一方面,其自身的尺寸也在随着.NET Framework的升级而不断膨胀。对于客户端应用(如 Windows Forms/WPF应用)来说,这个问题可以忽略,但是对于运行在移动设备和服务器上的应用(包括部署于云端应用)来说,由此带来的对性能和吞吐量的响应就成了一个不得不考虑的问题。

理想的 BCL 消费方式是“按需消费”,我们需要哪个部分就加载哪个部分。由于作为独立部署单元的程序集总是作为一个整体被 CLR 加载到内存中,所以要完全实现这种理想的 BCL消费方式,唯一的办法就是按照图 2-5 所示的方式将其划分为若干小的单元,并分别定义到独立的程序集中。除在运行的时候介绍内存占用外,按照模块化的原则对整个 BCL进行拆分也使维护和版本升级变得更加容易,如果现有版本具有需要修复的漏洞(Bug),或者性能需要改进,那么只需要改动并升级相应的模块即可。

图2-5 模块化的BCL

多个设备平台独自为政

目前,微软已经构建了一个完整且支持多种设备的.NET 生态系统,从最初单纯的桌面和服务器平台,逐渐扩展到移动、平板和游戏等平台。设备运行环境的差异性导致了针对它们的应用不能构建在一个统一的.NET Framework平台上,所以微软采用独立的.NET Framework平台对它们提供针对性的支持。就目前来说,除了支持 Windows 桌面和服务器设备的“全尺寸”的.NET Framework,微软还推出了一系列压缩版.NET Framework,包括Windows Phone、Windows Store、Silverlight和.NET Micro Framework等,它们分别对移动设备、平板设备和嵌入式设备提供支持。

这些.NET Framework并不是仅仅在AppModel层提供针对相应设备平台的开发框架,它们提供的 BCL和 Runtime也是不同的。换句话说,这些.NET Framework平台是完全独立的(见图 2-6)。由于目标平台具有独立性,所以很难编写能够在各个平台复用的代码,关于这一点笔者会在2.2节重点讨论。

图2-6.NET Framework平台之间的独立性

2.1.2 非Windows下的.NET

尽管微软多年来基本上仅研究Windows平台下的.NET,但是.NET 通过Mono和Xamarin已经延伸到其他平台(macOS、Linux、iOS和 Android等)。虽然目前做得并不算完美,但是可以认为.NET具备跨平台的能力。

从CLI谈起

.NET 跨平台的能力建立在一种开放的标准或者规范之上,这个所谓的标准或者规范就是CLI。制定CLI旨在解决这样一个问题:由不同(高级)编程语言开发的.NET应用能够在无须任何更改的情况下运行于不同的系统环境中。要实现这个目标,必须有效解决这里涉及的两种类型的差异,即编程语言的差异和运行时环境的差异。只有编程语言之间能够实现相互兼容,运行时环境能够得到统一,跨平台方可实现。

CLI的英文全称为Common Language Infrastructure,其中Common Language说的是语言,具体描述的是一种通用语言,旨在解决各种高级开发语言的兼容性问题。Infrastructure指的则是运行时环境,旨在弥补不同平台之间执行方式的差异。Common Language 是对承载应用的二进制内容的静态描述,Infrastructure 则表示动态执行应用的引擎,所以 CLI 旨在为可执行代码本身和执行它的引擎确立一个统一的标准。

编程语言可分为编译型和解释型两类。前者需要通过编译器实施编译以生成可执行代码,CLI涉及的 Common Language指的是编译型语言。要实现真正的跨平台,最终需要解决的是可执行代码在不同平台之间的兼容和可移植的问题,而编程语言的选择仅仅决定了应用程序源文件的原始状态,应用的兼容性和可移植性由编译后的结果决定。如果通过不同编程语言开发的应用通过相应的编译器编译后能够生成相同的目标代码,那么编程语言之间的差异就不再是一个问题。计算机领域有这样一种思维——任何一个软件设计方面的难题都可以通过增加一个抽象层来解决,CLI就贯彻了这一方针。

按照 CLI 的规定,用来描述可执行代码的是一种被称为 CIL(Common Intermediate Language)的语言,这是一种介于高级语言和机器语言之间的中间语言。如图 2-7 所示,虽然程序源文件由不同的编程语言编写,但是我们可以借助相应的编译器将其编译成 CIL 代码。从原则上讲,设计新的编程语言并将其加入.NET 中,只需要配以相应的编译器来生成统一的CIL 代码即可。我们也可以为现有的某种编程语言(如 Java)设计一种以 CIL 为目标语言的编译器,使之成为.NET 语言。CIL 是一门中间语言,也是一门面向对象的语言,所以对于一个CIL程序来说,类型是基本的组成单元和核心要素。微软制定的 CTS(Common Type System)为CLI确立了一个统一的类型系统。

图2-7 将不同语言编写的源代码编译成统一的中间代码

编程语言的差异通过编译器得以“同一化”,运行环境的差异则可以通过虚拟机(Virtual Machine,VM)技术来解决。虚拟机是 CIL的执行容器,能够在执行 CIL代码的过程中采用即时编译的方式将其动态地翻译成与当前执行环境完全匹配的机器指令。如图 2-8 所示,虚拟机屏蔽了不同操作系统之间的差异,使目标程序可以不做任何修改就能运行于不同的底层执行环境中,CIL实际上是一种面向虚拟机的语言。

图2-8 虚拟机采用即时编译的方式将CIL翻译成机器代码

从实现原理来看,让.NET能够跨平台其实不难,但是让各种相关的人员参与进来以构建一个健康而完善的跨平台.NET生态圈不是一件一蹴而就的事情,这里涉及的利益相关方包括编程语言的设计者,设计和开发编译器、虚拟机、IDE 以及其他相关工具的人员还包括广大的应用开发者。跨平台.NET生态环境必须建立在一个标准的规范之上,所以微软为此制定了CLI,然后提交给欧洲计算机制造商协会(European Computer Manufacturers Association,ECMA),成为一个编号为335的规范,所以CLI又被称为ECMA-335。另外,ECMA还接受了微软为C#这门编程语言制定的规范,即ECMA-334。

Mono与Xamarin

CLI(ECMA-335)在.NET 诞生的那一刻就被赋予了跨平台的“基因”,但微软似乎根本不曾想过将.NET推广到其他非Windows平台,真正完成这一使命的是一个叫作Mono的项目。虽然Mono不是一个新的项目,但是依然有很多人对其不甚了解。下面先对Mono进行简单介绍。

1999年,Miguel de Icaza创建了Ximian公司,该公司旨在为GNOME项目(这是一个为类UNIX系统提供桌面环境的 GNU项目,GNOME项目是目前 Linux最常用的桌面环境之一)开发软件和提供支持。2000年6月,微软正式发布.NET Framework,Miguel de Icaza被“基于互联网的全新开发平台”(.NET在发布之初被认为是“a new platform based on Internet standards”)深深吸引。

2000 年 11 月,微软发布了 CLI 规范(ECMA-335),并为公众开发了独立实现的许可,Miguel de Icaza认为CLI实际上为.NET走向非Windows平台提供了可能。Miguel de Icaza于2001年7月开启了Mono项目,并将C#作为主要的开发语言(目前支持VB.NET),所以针对CLI规范和C#语言的两个ECMA规范是构建Mono项目的理论基础,如果访问Mono的官方网站,可以了解Mono的定义:“Mono is an open source implementation of Microsoft's.NET Framework based on the ECMA standards for C#and the Common Language Runtime.”

Mono的使命不仅仅局限于将.NET应用正常运行在其他非Windows平台,它还可以帮助开发人员直接在其他平台进行.NET应用的开发,所以Mono不仅根据CLI规范为相应的平台开发了作为虚拟机的 CLR 和编译器,还提供了 IDE 和相应的开发工具(被称为 MonoDevelop)。Mono的第一个正式版本(Mono 1.0)发布于2004年6月。

2003年 8月,Ximian公司被 Novell公司收购。Novell公司继续支持 Miguel de Icaza开发Mono 项目,在这期间 Mono 陆续推出了若干 Mono 2.x 版本。2011 年 4 月,Novell 公司被Attachmate公司收购,后者决定放弃Mono项目,于是Miguel de Icaza带着整个Mono团队成立了 Xamarin公司。2011年 7月,Xamarin公司从原来的母公司 Novell得到了 Mono的开发许可。之后,Xamarin公司先后发布了 Mono 3.x、Mono 4.0和 Mono 5.x。Mono项目目前的目标是实现.NET 4.5除WPF、WF和部分WCF外的所有特性,目前缺失的部分的开发正在通过Olive项目(Mono的一个子项目)进行着。

在 Mono项目的基础之上,Xamarin公司开始开发以新公司命名的产品,其中最重要的版本是2013年2月发布的Xamarin 2.0。Xamarin 2.0由Xamarin.Android、Xamarin.iOS和Xamarin.Windows组成,可以采用 C#语言开发针对 Android、iOS 和 Windows 平台的 Native 应用。除此之外,Xamarin 2.0 还携带着一个被称为 Xamarin Studio(MonoDevelop 的升级版)的 IDE 以及与一些与Visual Studio集成的工具。2014年5月Xamarin 3.0发布,作为其核心的Xamarin.Forms为不同平台的Native应用提供统一的控件,也就是说,利用Xamarin.Forms API开发Native应用可以在无须做任何改变的情况下运行在Android、iOS和Windows平台上。

2016 年 2 月,微软和 Xamarin 公司签署协议并达成了前者针对后者的收购。在 2016 年Build 大会上,微软宣布将整个 Xamarin SDK 开源,并将它作为一个免费的工具集成到 Visual Studio中,Visual Studio企业版的用户还可以免费使用Xamarin企业版的所有特性。

综上所述,由于.NET是建立在CLI规范之上的,所以它具有跨平台的“基因”。在微软发布了第一个针对桌面和服务器平台的.NET Framework之后,它开始对完整版的.NET Framework进行不同范围和层次的“阉割”,进而出现了 Windows Phone、Windows Store、Silverlight 和.NET Micro Framework的压缩版的.NET Framework。从这个意义上讲,Mono和它们并没有本质上的区别,唯一不同的是,Mono真正突破了Windows平台的屏障。

包括 Mono 在内的这些分支促成了.NET 的繁荣,但这仅仅是一种表象而已。虽然都是.NET Framework的子集,但是由于它们采用完全独立的运行时和基础类库,所以很难开发一个支持多种设备的“可移植”(Portable)应用,这些分支反而成为制约.NET发展的一道道枷锁。