整本书都将强调,C#语言不能孤立地使用,而必须和.NET Framework一起考虑。C#编译器专门用于.NET,这表示用C#编写的所有代码总是在.NET Framework中运行。对于C#语言来说,可以得出两个重要的结论:
(1) C#的结构和方法论反映了.NET基础方法论。
(2) 在许多情况下,C#的特定语言功能取决于.NET的功能,或依赖于.NET基类。
由于这种依赖性,在开始编写C#程序前,了解.NET的体系结构和方法论就非常重要,这就是本章的目的所在。
C#是一种相当新的编程语言,C#的重要性体现在以下两个方面:
● 它是专门为与Microsoft的.NET Framework一起使用而设计的(.NET Framework是一个功能非常丰富的平台,可开发、部署和执行分布式应用程序)。
● 它是一种基于现代面向对象设计方法的语言,在设计它时,Microsoft还吸取了其他所有类似语言的经验,这些语言是近20年来面向对象规则得到广泛应用后才开发出来的。
有一个很重要的问题要弄明白:C#就其本身而言只是一种语言,尽管它是用于生成面向.NET环境的代码,但它本身不是.NET的一部分。.NET支持的一些特性,C#并不支持。而C#语言支持的另一些特性,.NET却不支持(如运算符重载)!
但是,因为C#语言和.NET一起使用,所以如果要使用C#高效地开发应用程序,理解Framework就非常重要,所以本章将介绍.NET的内涵。
.NET Framework的核心是其运行库执行环境,称为公共语言运行库(CLR)或.NET运行库。通常将在CLR控制下运行的代码称为托管代码(managed code)。
但是,在CLR执行编写好的源代码(使用C#或其他语言编写的代码)之前,需要编译它们。在.NET中,编译分为两个阶段:
(1) 将源代码编译为Microsoft中间语言(IL)。
(2) CLR把IL编译为平台专用的代码。
这个两阶段的编译过程非常重要,因为Microsoft中间语言是提供.NET的许多优点的关键。
Microsoft中间语言与Java字节码共享一种理念:它们都是低级语言,语法很简单(使用数字代码,而不是文本代码),可以非常快速地转换为本地机器码。对于代码,这种精心设计的通用语法有很重要的优点:平台无关性、提高性能和语言的互操作性。
首先,这意味着包含字节码指令的同一文件可以放在任一平台中,运行时编译过程的最后阶段可以很轻松地完成,这样代码就可以运行在特定的平台上。换言之,编译为中间语言就可以获得.NET平台无关性,这与编译为Java字节码就会得到Java平台无关性是一样的。
注意.NET的平台无关性目前只是停留在理论范畴,因为在编写本书时,.NET的完整实现只能用于Windows平台。不过,现在已经有了.NET的一个部分跨平台实现(参见Mono项目,它用于实现.NET的开放源代码,参见http://www.go-mono.com/)。
前面对IL和Java做了比较,实际上,IL比Java字节码的作用还要大。IL总是即时编译的(称为JIT编译),而Java字节码常常是解释性的。Java的一个缺点是,在运行应用程序时,把Java字节码转换为内部可执行代码的过程会导致性能的损失(但在最近,Java在某些平台上能进行JIT编译)。
JIT编译器并不是把整个应用程序一次编译完(这样会有很长的启动时间),而是只编译它调用的那部分代码(这是其名称由来)。代码编译过一次后,得到的本地可执行程序就存储起来,直到退出该应用程序为止,这样在下次运行这部分代码时,就不需要重新编译了。Microsoft认为这个过程要比一开始就编译整个应用程序代码的效率高得多,因为任何应用程序的大部分代码实际上并不是在每次运行期间都执行。使用JIT编译器,从来都不会编译这种代码。
这解释了为什么托管IL代码几乎和本地机器代码的执行速度一样快,但是并没有说明为什么Microsoft认为这会提高性能。其原因是编译过程的最后一部分是在运行时进行的,JIT编译器确切地知道程序运行在什么类型的处理器上,可以利用该处理器提供的任何特性或特定的机器代码指令来优化最后的可执行代码。
传统的编译器会优化代码,但它们的优化过程是独立于运行代码的特定处理器的。这是因为传统的编译器是在发布软件之前编译为本地机器可执行的代码。即编译器不知道运行代码的处理器的类型,例如该处理器是兼容x86的处理器还是Alpha处理器,这超出了基本操作的范围。
使用IL不仅支持平台无关性,还支持语言的互操作性。简而言之,就是能将任何一种语言编译为中间语言,编译为中间语言的代码可以与从其他语言编译过来的代码进行交互操作。
那么除了C#之外,还有什么语言可以通过.NET进行交互操作呢?下面就简要讨论其他常见语言如何与.NET交互操作。
VisualBasic 6在升级到Visual Basic .NET 2002时,经历了一番脱胎换骨的变化,才集成到.NET Framework的第1版中。VisualBasic 语言对Visual Basic 6进行了很大的演化,也就是说,Visual Basic 6并不适合运行.NET程序。例如,它与COM(ComponentObject Model,组件对象模型)的高度集成,并且只把事件处理程序作为源代码显示给开发人员,大多数代码隐藏不能用作源代码。另外,它不支持继承的实现,Visual Basic 6使用的标准数据类型也与.NET不兼容。
Visual Basic 6在2002年升级为VisualBasic .NET,对Visual Basic进行的改变非常大,完全可以把Visual Basic .NET当成一种新语言。已有的VisualBasic 6代码不能编译为当前的Visual Basic 2012代码(或Visual Basic .NET2002、2003、2005、2008和2012代码),把Visual Basic6程序转换为Visual Basic 2010时,需要对代码进行大量的改动。但大多数修改工作都可以由Visual Studio2012(Visual Studio的升级版本,用于与.NET一起使用)自动完成。如果把VisualBasic 6项目读到Visual Studio 2012中,Visual Studio 2012就会自动升级该项目,也就是说把Visual Basic 6源代码重写为Visual Basic 2012源代码。虽然这意味着其中的工作大大减轻,但用户仍需要检查新的VisualBasic 2012代码,以确保项目仍可按预期方式正确工作,因为这种转换并不能达到完美无缺的程度。
这种语言升级的一个副作用是不能再把Visual Basic 2012编译为本地可执行代码了。VisualBasic 2012只编译为中间语言,就像C#一样。如果需要继续使用Visual Basic 6编写程序,就可以这么做,但生成的可执行代码会完全忽略.NET Framework,如果继续把Visual Studio作为开发环境,就需要安装VisualStudio 6。
VisualC++ 6有许多Microsoft对Windows的特定扩展。Visual C++ .NET又新增了更多的扩展内容来支持.NET Framework。现有的C++源代码会继续编译为本地可执行代码,而不会有修改,但它会独立于.NET运行库运行。如果让C++代码在.NET Framework中运行,就可以在代码的开头添加下述命令:
#using
还可以把标记/clr传递给编译器,这样编译器假定要编译托管代码,因此会生成中间语言,而不是本地机器码。C++的一个有趣的问题是在编译成托管代码时,编译器可以生成包含内嵌本地可执行程序的IL。这表示在C++代码中可以把托管类型和非托管类型合并起来,因此托管C++代码:
class MyClass
{
定义了一个普通的C++类,而代码:
ref class MyClass
{
生成了一个托管类,就好像使用C#或Visual Basic 2012编写类一样。实际上,托管C++代码比C#代码更优越的一点是可以在托管C++代码中调用非托管C++类,而不必采用COM互操作功能。
如果在托管类型上试图使用.NET不支持的特性(例如,模板或类的多继承),编译器就会出现一个错误。另外,在使用托管类时,还需要使用非标准C++功能。
编写使用.NET的C++程序会得到几种不同的互操作场景。使用编译器设置/clr启用公共语言运行库支持时,就可以完全混合所有的本地和托管C++功能。其他选项(如/clr:safe和/clr:pure)可以限制C++指针的使用,从而像使用C#和Visual Basic那样编写安全的代码。
Visual C++ 2012允许为Windows 8的Windows Runtime(WinRT)创建程序。在这样的程序中,C++不使用托管代码,而是本地访问WinRT。
从技术上讲,COM和COM+并不是面向.NET的技术,因为基于它们的组件不能编译为IL(但如果原来的COM组件是用C++编写的,那么使用托管C++在某种程度上可以这么做)。但是,COM+仍然是一个重要工具,因为它包含一些.NET不具备的特性。另外,COM组件仍可以使用——.NET集成了COM的互操作性,从而使托管代码可以调用COM组件,COM组件也可以调用托管代码(见第23章)。一般情况下,把新组件编写为.NET组件,大多是为了方便,因为这样可以利用.NET基类和托管代码的其他优点。
Windows 8提供了一种新的运行库,可被新应用程序使用。这个运行库可在VisualBasic、C#、C++和JavaScript中使用。用在不同的环境中时,它会发生相应的变化。例如,在C#中使用时,它看起来就像.NETFramework中的类;在JavaScript中使用时,它看起来就像JavaScript开发人员所惯用的JavaScript库;而在C++中使用时,它又像是一个C++标准库。这种多样性是通过使用语言投影实现的。第31章将讨论Windows运行库以及在C#中如何使用它。