C#高级编程(第10版) C# 6 & .NET Core 1.0 (.NET开发经典名著)
上QQ阅读APP看书,第一时间看更新

1.3 .NET 2015

.NET 2015是所有.NET技术的总称。图1-1给出了这些技术的总图。左边代表.NET Framework 4.6技术,如WPF和ASP.NET 4。ASP.NET Core 1.0也可以在.NET Framework 4.6上运行。右边代表新的.NET Core技术。ASP.NET Core 1.0和UWP运行在.NET Core上。还可以创建在.NET Core上运行的控制台应用程序。

.NET Core的一个组成部分是一个新的运行库CoreCLR。这个运行库从ASP.NET Core 1.0开始使用。不使用CoreCLR运行库,.NET也可以编译为本地代码。UWP自动利用这个特性,这些.NET应用程序编译为本地代码之后,在Windows Store中提供。也可以把其他.NET Core应用程序以及运行在Linux上的应用程序编译为本地代码。

图1-1

在图1-1的下方,.NET Framework 4.6和.NET Core 1.0之间还有一些共享的内容。运行库组件是共享的,如垃圾回收器的代码和RyuJIT(这是一个新的JIT编译器,把IL代码编译为本地代码)。垃圾回收器由CLR、CoreCLR和.NET Native使用。RyuJIT即时编译器由CLR和CoreCLR使用。库可以在基于.NET Framework 4.6和.NET Core 1.0的应用程序之间共享。NuGet包的概念帮助把这些库放在一个在所有.NET平台上都可用的公共包上。当然,所有这些技术都使用新的.NET编译器平台。

1.3.1 .NET Framework 4.6

.NET Framework 4.6是.NET Framework在过去10年不断增强的结果。1.2节讨论的许多技术都基于这个框架。这个框架用于创建Windows Forms和WPF应用程序。此外,ASP.NET 5可以在.NET Core上运行,也可以在.NET Framework 4.6上运行。

如果希望继续使用ASP.NET Web Forms,就应选择ASP.NET 4.6和.NET Framework 4.6。ASP.NET 4.6与4.5版本相比,也有新特性,比如支持HTTP2(HTTP协议的一个新版本,参见第25章),用Roslyn编译器编译,以及异步模型绑定。然而,不能把ASP.NET Web Forms切换到.NET Core。

在目录%windows%\Microsoft.NET\Framework\v4.0.30319下可以找到框架的库以及CLR。

可用于.NET Framework的类组织在System名称空间中。表1-2描述的名称空间提供了层次结构的思路。

表1-2

注意:一些新的.NET类使用以Microsoft开头而不是以System开头的名称空间,比如用于Entity Framework的Microsoft.Data.Entity,用于新的依赖关系注入框架的Microsoft.Extensions.DependencyInjection。

1.3.2 .NET Core 1.0

.NET Core 1.0是新的.NET,所有新技术都使用它,是本书的一大关注点。这个框架是开源的,可以在http://www.github.com/dotnet上找到它。运行库是CoreCLR库;包含集合类的框架、文件系统访问、控制台和XML等都在CoreFX库中。

.NET Framework要求必须在系统上安装应用程序需要的特定版本,而在.NET Core 1.0中,框架(包括运行库)是与应用程序一起交付的。以前,把ASP.NET Web应用程序部署到共享服务器上有时可能有问题,因为提供程序安装了旧版本的.NET。这种情况已经一去不复返了。现在可以同时提交应用程序和运行库,而不依赖服务器上安装的版本。

.NET Core 1.0以模块化的方式设计。该框架分成数量很多的NuGet包。根据应用程序决定需要什么包。添加新功能时,.NET Framework就变得越来越大。删除不再需要的旧功能是不可能的,比如添加了泛型集合类,旧的集合类就是不必要的。.NET Remoting被新的通信技术取代,或LINQ to SQL已经更新为Entity Framework。删除某个功能,会破坏应用程序。这不适用于.NET Core,因为应用程序会发布它需要的部分框架。

目前.NET Core的框架与.NET Framework 4.6一样庞大。然而,这可以改变,它可以变得更大,但因为模块化,其增长潜力不是问题。.NET Core已经如此之大,本书不可能包括每个类型。在http://www.github.com/dotnet/corefx中可以看到所有的源代码。例如,旧的非泛型集合类已被包含在.NET Core中,使旧代码更容易进入新平台。

.NET Core可以很快更新。即使更新运行库,也不影响现有的应用程序,因为运行库与应用程序一起安装。现在,微软公司可以增强.NET Core,包括运行库,发布周期更短。

注意:为了使用.NET Core开发应用程序,微软公司创建了新的命令行实用程序.NET Core Command Line (CLI)。

1.3.3 程序集

.NET程序的库和可执行文件称为程序集(assembly)。程序集是包含编译好的、面向.NET Framework的代码的逻辑单元。

程序集是完全自描述性的,它是一个逻辑单元而不是物理单元,这意味着它可以存储在多个文件中(动态程序集存储在内存中,而不是存储在文件中)。如果一个程序集存储在多个文件中,其中就会有一个包含入口点的主文件,该文件描述了程序集中的其他文件。

可执行代码和库代码使用相同的程序集结构。唯一的区别是可执行的程序集包含一个主程序入口点,而库程序集不包含。

程序集的一个重要特征是它们包含的元数据描述了对应代码中定义的类型和方法。程序集也包含描述程序集本身的程序集元数据,这种程序集元数据包含在一个称为“清单(manifest)”的区域中,可以检查程序集的版本及其完整性。

由于程序集包含程序的元数据,因此调用给定程序集中的代码的应用程序或其他程序集不需要引用注册表或其他数据源就能确定如何使用该程序集。

在.NET Framework 4.6中,程序集有两种类型:私有程序集和共享程序集。共享程序集不适用于UWP,因为所有代码都编译到一个本机映像中。

1.私有程序集

私有程序集一般附带在某个软件上,且只能用于该软件。附带私有程序集的常见情况是,以可执行文件或许多库的方式提供应用程序,这些库包含的代码只能用于该应用程序。

系统可以保证私有程序集不被其他软件使用,因为应用程序只能加载位于主执行文件所在文件夹或其子文件夹中的私有程序集。

用户一般会希望把商用软件安装在它自己的目录下,这样软件包不存在覆盖、修改或在无意间加载另一个软件包的私有程序集的风险。私有程序集只能用于自己的软件包,这样,用户对什么软件使用它们就有了更大的控制权。因此,不需要采取安全措施,因为这没有其他商用软件用某个新版本的程序集覆盖原来私有程序集的风险(但软件专门执行怀有恶意的损害性操作的情况除外)。名称也不会有冲突。如果私有程序集中的类正巧与另一个人的私有程序集中的类同名,是不会有问题的,因为给定的应用程序只能使用它自己的一组私有程序集。

因为私有程序集是完全自包含的,所以部署它的过程就很简单。只需要把相应的文件放在文件系统的对应文件夹中即可(不需要注册表项),这个过程称为“0影响(xcopy)安装”。

2.共享程序集

共享程序集是其他应用程序可以使用的公共库。因为其他软件可以访问共享程序集,所以需要采取一定的保护措施来防止以下风险:

● 名称冲突,指另一个公司的共享程序集实现的类型与自己的共享程序集中的类型同名。因为客户端代码理论上可以同时访问这些程序集,所以这是一个严重的问题。

● 程序集被同一个程序集的不同版本覆盖——新版本与某些已有的客户端代码不兼容。

这些问题的解决方法是把共享程序集放在文件系统的特定子目录树中,称为全局程序集缓存(Global Assembly Cache, GAC)。与私有程序集不同,不能简单地把共享程序集复制到对应的文件夹中,而需要专门安装到缓存中。有许多.NET工具可以完成这个过程,并要求对程序集进行检查,在程序集缓存中设置一个小的文件夹层次结构,以确保程序集的完整性。

为了避免名称冲突,应根据私钥加密法为共享程序集指定一个名称(而对于私有程序集,只需要指定与其主文件名相同的名称即可)。该名称称为强名(strong name),并保证其唯一性,它必须由要引用共享程序集的应用程序来引用。

与覆盖程序集的风险相关的问题,可以通过在程序集清单中指定版本信息来解决,也可以通过同时安装来解决。

1.3.4 NuGet包

在早期,程序集是应用程序的可重用单元。添加对程序集的一个引用,以使用自己代码中的公共类型和方法,此时,仍可以这样使用(一些程序集必须这样使用)。然而,使用库可能不仅意味着添加一个引用并使用它。使用库也意味着一些配置更改,或者可以通过脚本来利用的一些特性。这是在NuGet包中打包程序集的一个原因。

NuGet包是一个zip文件,其中包含程序集(或多个程序集)、配置信息和PowerShell脚本。

使用NuGet包的另一个原因是,它们很容易找到,它们不仅可以从微软公司找到,也可以从第三方找到。NuGet包很容易在NuGet服务器http://www.nuget.org上获得。

在Visual Studio项目的引用中,可以打开NuGet包管理器(NuGet Package Manager,见图1-2),在该管理器中可以搜索包,并将其添加到应用程序中。这个工具允许搜索还没有发布的包(包括预发布选项),定义应该在哪个NuGet服务器中搜索包。

图1-2

注意:使用NuGet服务器中的第三方包时,如果一个包以后才能使用,就总是有风险。还需要检查包的支持可用性。使用包之前,总要检查项目的链接信息。对于包的来源,可以选择Microsoft and .NET,只获得微软公司支持的包。第三方包也包括在Microsoft and .NET部分中,但它们是微软公司支持的第三方包。

也可以让开发团队使用自己的NuGet服务器。可以定义开发团队只允许使用自己服务器中的包。

因为.NET Core是模块化的,所以所有应用程序(除了最简单的应用程序)都需要额外的NuGet包。为了更容易找到包,本书使用.NET Core构建的每个示例应用程序都显示了一个表格,列出需要添加的包和名称空间。

注意:NuGet包管理器的更多信息参见第17章。

1.3.5 公共语言运行库

UWP(通用Windows平台)利用Native .NET把IL编译成本地代码。在所有其他场景中,使用.NET Framework 4.6的应用程序和使用.NET Core 1.0的应用程序都需要CLR(Common Language Runtime,公共语言运行库)。然而,.NET Core使用CoreCLR,而.NET Framework使用CLR。

在CLR执行应用程序之前,编写好的源代码(使用C#或其他语言编写的代码)都需要编译。在.NET中,编译分为两个阶段:

(1) 将源代码编译为Microsoft中间语言(Intermediate Language, IL)。

(2) CLR把IL编译为平台专用的本地代码。

IL代码在.NET程序集中可用。在运行时,JIT编译器编译IL代码,创建特定于平台的本地代码。

新的CLR和CoreCLR包括一个新的JIT编译器RyuJIT。新的JIT编译器不仅比以前的版本快,还在用Visual Studio调试时更好地支持Edit & Continue特性。Edit & Continue特性允许在调试时编辑代码,可以继续调试会话,而不需要停止并重新启动过程。

CLR还包括一个带有类型加载器的类型系统,类型加载器负责从程序集中加载类型。类型系统中的安全基础设施验证是否允许使用某些类型系统结构,如继承。

创建类型的实例后,实例还需要销毁,内存也需要回收。CLR的另一个功能是垃圾回收器。垃圾回收器从托管堆中清除不再引用的内存。第5章解释其工作原理和执行的时间。

CLR还负责线程的处理。在C#中创建托管的线程不一定来自底层操作系统。线程的虚拟化和管理由CLR负责。

注意:如何在C#中创建和管理线程参见第21章和第22章。

1.3.6 .NET Native

.NET Native是.NET 2015的一个新特性,它将托管程序编译成本地代码。对于Windows应用程序,这会生成优化的代码,其启动时间可以缩短60%,内存的使用减少15%~20%。

最初,.NET Native把UWP应用编译为本地代码,以部署到Windows Store。现在,.NET Native将来也可以用于其他.NET Core应用程序,不过它目前还不能用于.NET Core 1.0版本中,但可用于.NET Core的将来版本中。可以把运行在Windows和Linux上的.NET Core应用程序编译为本地代码。当然,在每一个平台上需要不同的本地映像。在后台.NET Native共享C++优化器,以生成本地代码。

1.3.7 Windows运行库

从Windows 8开始,Windows操作系统提供了另一种框架:Windows运行库(Windows Runtime)。这个运行库由WUP(Windows Universal Platform, Windows通用平台)使用,Windows 8使用第1版,Windows 8.1使用第2版,Windows 10使用第3版。

与.NET Framework不同,这个框架是使用本地代码创建的。当它用于.NET应用程序时,所包含的类型和.NET类似。在语言投射的帮助下,Windows运行库可以用于JavaScript、C++和.NET语言,它看起来像编程环境的本地代码。不仅方法因区分大小写而行为不同;方法和类型也可以根据所处的位置有不同的名称。

Windows运行库提供了一个对象层次结构,它在以Windows开头的名称空间中组织。这些类没有复制.NET Framework的很多功能;相反,提供了额外的功能,用于在UWP上运行的应用程序。如表1-3所示。

表1-3