L
Lera
Unregistered / Unconfirmed
GUEST, unregistred user!
展现 C#
展现 C#
前言
0.1 提要
欢迎阅读《展现 C#》(Presenting C#)。这本书是你提高企业编程语言的一条捷径。
这种企业编程语言带有下一代编程语言服务运行时(NGWS Runtime):C#(发音"C sharp")。
NGWS Runtime 是一个不仅管理执行代码、同时也提供使编程更加容易的动态环境。
编译器产生受管代码以指向这种受管理执行环境。你获得跨平台语言集成、跨平台语言
异常处理、增强安全性、版本控制、安排支持和查错服务。
支持NGWS Runtime 的主要语言是C#。支持NGWS框架的很多程序是用C#写的,因此,
在一些带有NGWS Runtime的编译器中,它的编译器可以被认为是非常经得起测试且是
经过优化的。C#语言借鉴了C++,但是具备现代化和新增的类型安全——使C#成为企业
解决方案的首选语言。
0.2 谁应该读这本书
如果你对编程很陌生,这本书不适合你。这本书企图让程序员解脱并使用C#,
基于他们已经拥有的知识。《展现 C#》的目标瞄准了已经具有如C/C++、VB、Java
或其它编程经验的程序员。
如果你具有C++的背景,转向C#会很容易,但是,如果你精通于其它不同的语言,
这本书也会耗掉你的一些日子。如果你有一点COM编程的知识,这本会更有趣,
但会COM编程不再是必要的。
0.3 这本书如何组织
第一章 C#简介——这一章把你带到C#逛一回,同时回答了有关你应该考虑
学习C#的问题。
第二章 NGWS Runtime 的技术基础——介绍NGWS Runtime为运行的C#代码所
提供的低层架构。
第三章 第一个C#应用程序——创建真正第一个C#应用程序(否则它是什么呢?),
它就是一个"Hello World"应用程序。
第四章 C#类型——可以看到用在C#应用程序中的各种类型。探索值型和引用型
的区别,还有如何加框和消框。
第五章 类——C#功能真正强大的标志,它是具有类的面向对象的编程。你会学
到好多好多的东西,如构造函数、析构函数、方法、属性、索引和事件等。
第六章 控制语句——对应用程序实行流程控制。探索C#提供的各种选择和陈述
语句。
第七章 异常处理——获得了编写实现异常处理的应用程序技巧,拥有这种技巧,
在NGWS Runtime世界,你是一个好的公民
第八章 用C#写组件——用C#构建组件,因为支持NGWS Runtime,它可以被不同
语言平台的客户使用。
第九章 配置和安排——学到如何用C#有条件地进行编译工作,和如何为你的C#
源码自动地创建文档。另外,这一章将介绍到NGWS的版本技术问题。
第十章 用非受管代码交互操作——发现你如何使用C#内部的非受管代码,且非
受管代码如何与你的C#组件一起交互操作。
第十一章 C#代码查错——获得使用SDK查错工具的技巧,可以在应用程序中准确
地找到错误并修改它们。
第十二章 安全——探索NGWS Runtime的安全概念。学到代码访问安全性和基于
角色的安全性。
0.4 使用这本书你需要什么?
从这本书的观点看,你所需要的就是下一代windows服务软件开发包(NGWS SDK)。
尽管至少只要有NGWS Runtime 和C#编译器就可以,但当探索这些激动人心的新技术功能
时,在一台机器上装有说明文件档和所有的SDK工具(包括debugger),将是一个极好的主意。
这本书不需要你装在机器上的Visual Studio 7的任何工具。我仅建议你要有一个
优秀的支持行数的程序编辑器,用以编辑C#源文件。
……
(省略掉一些有关出版过程和表示感谢的段落)
0.5 关于作者
Christoph Wille,微软系统工程师(MCSE)、微软系统开发师(MCSD)、Netware网络
管理员(CNA)和MCP-IT,作为一个网络咨询人员和程序员,特别精通Windows DNA。微软
认为他是ASP方面最有价值的人(MVP),他是和微软在一起使用早期的C#版本的少数开发者
之一。
Christoph 独自或参与了几本书的写作,包括Sams出版社的《自学ADO2.5 21天》、
《自学ASP 24小时》、《自学MCSE TCP/IP 14天》和《MCSE 训练指南:SQL Server 7
管理》。
第三章 第一个C#应用程序
3.0 选择一个编辑器
尽管我是一个顽固的Notepad狂,但这次我不建议用它编辑源码。原因是
你正在与真正的编程语言打交道,使用Notepad编辑源码编译时可能产生大量的错误
信息行(C++程序员知道我在说什么。)
你有几种选择。可以重新配置你信任的老式Visual C++ 6.0,使它能够和C#
源文件一起工作。第二种选择是使用新的Visual Studio 7。第三,你可以用任何第三方
程序编辑器,最好要支持行数、色彩编码、工具集成和良好的搜索功能。CodeWright就是
其中一个例子,如图3.1所示。
图3.1 CodeWright 是你可以用于创建C#代码文件众多可能编辑器中的一个。
当然,在所提到的编辑器中,没有一个对创建C#程序来说是必要的。用Notepad
肯定可以编辑。但是,如果你考虑到要编写更大的项目,最好还是忍痛割爱吧。
3.1 "Hello World" 代码
讨论编辑器有点离题 ,让我们把话题转回到一个非常出名的小应用程序。这个
最短的C#版本应用程序见清单3.1。把它存起来,文件名为 helloworld.cs,以便使你能按
照说明,完成诸如编译应用程序等其它余下来的步骤。
清单 3.1 最简单的 "Hello World "程序
1: class HelloWorld
2: {
3: public static void Main()
4: {
5: System.Console.WriteLine("Hello World");
6: }
7: }
在C#中,代码块(语句组)由大括弧({和})所括住。所以,甚至你以前没有C++的
经验,你也可以说出Main()方法就是HelloWorld 类语句的一部分,因为类被括在所定义
的大括弧中。
C#应用程序(可执行)的入口点就是 static Main 方法,它必须包含在一个类中。
仅有一个类能使用该标志定义,除非你告诉编译器它应使用哪一个 Main 方法(否侧,
在这个方法中,你的程序开始并结束。方法中可以调用其它方法——如这个例子中,
用于输出文本——或者创建对象并激活该方法。
正如你所看到的,Main方法返回一个void类型。
public static void Main()
尽管看到这些语句时,C++程序员肯定会觉得似曾相识,但是其他程序员并不如此。首先,public 的访问标志告诉我们这个方法可以被任何程序访问,这是它被调用的必要条件。其次,static 意味着没有先创建类的实例也可以调用方法——你所要做的就是用类名调用方法。
HelloWorld.Main();
但是,我不赞成在Main方法中执行这行代码,递归会导致堆栈溢出。
另一重要的方面是返回类型。对于方法Main,可选择void (意味着根本就
没有返回值),或用int 为整型结果(应用程序返回的错误级别)。因此,两种可能的Main
方法为:
public static void Main()
public static int Main()
C++程序员会同样知道后面我要提到的——可以传给应用程序的命令行参数数组。
如:
public static void Main(string[] args)
我现在并不想详细地说明如何访问参数,但我想事先给C++程序员一个警告:
和C++相比,应用程序路径不是这个数组的一部分。仅仅那些参数包含在这个数组中。
在对Main方法并不简短的介绍之后,让我们把注意力集中到唯一真正的代码行
——这行代码在屏幕上显示"Hello Wold"。
System.Console.WriteLine("Hello World");
假如不是由于有了System,大家会马上猜到WriteLine是Console 对象的一个
静态方法。那么System代表什么呢? 它是包含Console对象的名字空间(范围),实际上
并不是每次都在Console对象前加上名字空间的前缀,你可以象清单3.2所示范的那样,
应用程序中引入名字空间。
清单3.2 在应用程序中引入名字空间
1: using System;
2:
3: class HelloWorld
4: {
5: public static void Main()
6: {
7: Console.WriteLine("Hello World");
8: }
9: }
所有你要做的就是给System名字空间加一个using指令。在这之后,不再需要
规定名字空间,就可以使用它们的方法和属性了。NGWS 框架体系中有很多的名字空间,
我只对巨大的名字空间池中的少数几个对象进行探讨。但在第八章 "用C#写组件"将介绍
为你的对象创建自己的名字空间。
3.2 编译应用程序
由于NGWS Runtime支持所有的编译器(VB、C++和C#),你不必买一个单独的
开发工具用来把应用程序编译成IL(中间语言)。但是,如果你从没有用过命令行编译器
编译过应用程序(仅懂得编译名,而没有熟记), 它还是你的首要选择。
打开命令提示符并切换到存 helloworld.cs 的目录。敲入以下命令:
csc helloworld.cs
helloworld.cs 被编译并链接成hellworld.exe。因为源码没有错误(那当然!),
C#编译器没有出错提示,在整个编译过程没有丝毫停顿。如图3.2所示。
图3.2 使用命令行编译器 csc.exe 编译应用程序
现在你已经准备好运行第一个真正用C#编写的应用程序。简单地在命令行上
敲入helloworld,输出结果为 "Hello World"。
在继续往下介绍之前, 我想稍为想象一下第一个应用程序和一个编译器开关的
使用:
csc /out:hello.exe helloworld.cs
这个开关告诉编译器输出文件命名为hello.exe。虽然这不是什么绝招,但它是
这本书中用到的未来编译器的基本功。
3.3 输入和输出
到目前为止,我仅仅演示了把简单的常量字符串输出到屏幕。尽管这本书只
介绍了C#编程的概念而不介绍用户接口编程,但我需要让你迅速学会简单的屏幕输入和
输出方法——相应于C的scanf 和 printf,或者C++的cin 和cout。我不能提供VB相应的
函数,因为屏幕访问不是该核心语言的一部分。
你只需要能够读用户的输入并提示一些信息给用户。清单3.3 说明如何读
一个用户请求的名字输入,并显示一条已定制好的"Hello" 信息。
Listing 3.3 从控制台读输入信息
1: using System;
2:
3: class InputOutput
4: {
5: public static void Main()
6: {
7: Console.Write("Please enter your name: ");
8: string strName = Console.ReadLine();
9: Console.WriteLine("Hello " + strName);
10: }
11: }
第7行使用Console对象的一个新方法用于提示文本信息给用户,它就是Write方法。
它与WriteLine不同的地方在于它输出时不换行。我使用这种方法以便用户可以在信息提示
的同一行输入名字。
在用户输入他的名字后(并按回车键),ReadLine 方法读入了一个字符串变量。
名字字符串连接到常量字符串"Hello",并用我们早已熟悉的WriteLine方法显示出来
(见图3.2)。
图3.3 编译和运行定制的Hello 应用程序
你几乎已学完了NGWS框架必要的输入和输出功能。但是,你还需要为用户显示
多个值。为用户写一个格式串。清单3.4展示一个例子。
清单 3.4 使用不同的输出方法
1: using System;
2:
3: class InputOutput
4: {
5: public static void Main()
6: {
7: Console.Write("Please enter your name: ");
8: string strName = Console.ReadLine();
9: Console.WriteLine("Hello {0}",strName);
10: }
11: }
第9行包含了使用格式串的Console.WriteLine语句。格式串例子如下:
"Hello {0}"
{0}代替WriteLine方法的参数表中紧随格式串后的第一个变量。你可以用该
技术格式化超过三个变量。
Console.WriteLine("Hello {0} {1}, from {2}",
strFirstname, strLastname, strCity);
当然,并不仅限于只使用字符串变量。你可以使用任何类型,这些类型在
后面的第四章 "C#类型"中有讨论。
3.4 添加注释
当写代码时,你应为代码写注释条文,解释实现的内容、变更史等。
尽管你注释中提供的信息(如果有的话)是给你写的,但是你还是必须遵守写C#注释
的方法。清单3.5 显示采用的两种不同的方式。
清单3.5 给你的代码添加注释
1: using System;
2:
3: class HelloWorld
4: {
5: public static void Main()
6: {
7: // 这是单行注释
8: /* 这种注释
9: 跨越多行 */
10: Console.WriteLine(/*"Hello World"*/);
11: }
12: }
"//" 符号用于单行注释。你可以用"//"注释当前所在行,或是跟在一个代码
语句的后面:
int nMyVar = 10;
// 胡说八道
所有在"//"后面的被认为是一条注释;所以,你可以同样用它们来注释一整行
或一行源代码的部分。这种注释方式同C++中介绍的相似。
如果你的注释跨越多行,必须使用"/* */"的字符组合。这种方式在C中有效。
除了单行注释外,这种方式在C++和C#中还同样有效。因C/C++和C#都使用这种多行注释
方式,所以它们也使用相同的终结符。请看下列代码行:
/* Console.WriteLine("Hello World");
*/
我使用"/* */"简单地注释一整行。现在我假定这一行是很长代码的一部分,
而且我决定要暂时禁用一个程序块:
/*
...
/* Console.WriteLine("Hello World");
*/
...
*/
这个结构所存在的问题为: "Hello World"那一行后面的"*/"终止了始于
第一行的"/*"的注释,余下的代码对编译器有效,你将看到一些有趣的出错信息。
至少 最后的"*/"被标志为归属错误。我只不过想提醒一下,让你了解这种错误。
3.5小结
在这一章中,你创建、编译并执行了第一个C#应用程序:
著名的"Hello World"程序。我用这个短短的应用程序给你介绍有关Main方法,
它是一个应用程序的入口点,也是出口点。这个方法可以没有返回值或返回一个
整数错误级别。如果你的应用程序用参数调用,你可以(但不必要)读出并使用它们。
在编译和测试应用程序后,你学到了更多的由Console对象提供的有关输入
和输出的方法。对于学习C#而言,它们足以创建出有意义的控制台例子,但用户接口
的大部分将是WFC、WinForms或者ASP+。
第一章 C# 简介
欢迎您加入C#的世界! 这一章将把您引进C#的天地,并回答一些相关的问题,如:
您为什么要使用C#,C++和C#的主要有什么不同点,以及为什么C#使开发更容易而且
还使您感到很有趣。
为什么是另外一种编程语言?
必须回答的一个问题:当您已经使用C++或VB从事企业开发时,为什么还要学习
另一种语言? 市场式的回答就是:"在企业计算领域,C#将会变成为用于编写
"下一代窗口服务"(Next Generation Windows Services,简写为NGWS )应用程序
的主要语言。" 这一章将对用参数请求提供支持,并陈列了C#的一些功能。
这一章会使您开胃的。
C#语言自C/C++演变而来。但是,它现代、简单、完全面向对象和类型安全。
如果您是C/C++程序员,学习曲线将会很平坦。许多C#语句直接借用您所喜爱的语言,
包括表达式和操作符。假如不仔细看,简直会把它当成C++。
关于C#最重要的一点:它是现代的编程语言。它简化和现代化了C++在类、
名字空间、方法重载和异常处理等领域。屏弃了C++的复杂性,使它更易用、
更少出错。
对C#的易用有贡献的是减少了C++的一些特性,不再有宏、模板和多重继承。
特别对企业开发者来说,上述功能只会产生更多的麻烦而不是效益。
使编程更方便的新功能是严格的类型安全、版本控制、垃圾收集(garbage
collect)等等。所有的这些功能的目标都是瞄准了开发面向组件的软件。
在继续呈现出更多的功能之前,我想停下来并在下面说明C#至关重要的各种要素。
简单
现代
面向对象
类型安全
版本控制
兼容
灵活
简单
C#具有C++所没有的一 个优势就是学习简单。该语言首要的目标就是简单。
很多功能(还不如说是缺少了C++的一些功能)有助于C#全方位的简单。
在C#中,没有C++中流行的指针。默认地,您工作在受管理的代码中,
在那里不允许如直接存取内存等不安全的操作。我想没有C++程序员可以声称,
从没有使用指针访问过不属于他们的内存。
与指针"戏剧性"密切相关的是"愚蠢的"操作。在C++中,有::、.、和->操作符,
它们用于名字空间、成员和引用。对于新手来说,操作符至今仍是学习的一道难关。C#弃用其它操作符,仅使用单个操作符 "."。现在一个程序员所需要理解的就是嵌套名字的注解了。
您不必记住基于不同处理器架构的隐含的类型,甚至各种整型的变化范围。
C#使用统一的类型系统,屏弃了C++多变的类型系统。这种系统充许您把各种类型
作为一个对象查看,它是一个原始类型还是一个full-blown 类。和其它编程语言相比,由于加框(boxing)和消框(unboxing)的机制,把简单类型当作对象处理并不能获得性能的改善。稍后将详细解释加框和消框,但基本上仅当需要时才使用对象访问简单类型这种技术。
首先,老练的程序员可能不喜欢它,但是整型和布尔型如今终归是两种完全不同的
数据类型。这就意味着原来if语句中错误的赋值现在会被编译出错,因为if语句只
接受布尔类型的值。再也不会出现误用赋值符为比较符这样的错误!
C#同时也解决了存在于C++中已经有些年头的多余东西(redundancies)。这种多余
包括常数预定义,不同字符类型等。鉴于多余表单已经从该语言中消失,故一般在C#中
都可以使用表单了。
现代
您投入学习C#的努力是一笔大投资,因为C#是为编写NGWS 应用程序的主要语言而设计。
您 将会发现很多自己用C++可以实现或者很费力实现的功能,在C#中不过是一部分
基本的功能而已。
对于企业级的编程语言来说,新增的金融数据类型很受欢迎。您用到了一种新的
十进制数据类型,它专用于金融计算方面。如果不喜欢这种现成简单的类型,
根据您应用程序的特殊需求,可以很容易地创建出新的一种数据类型。
我已经提到,指针不再是您编程武器的一部分。不要太惊讶,全面的内存管理已经
不是您的任务。运行时NGWS提供了一个垃圾收集器,负责C#程序中的内存管理。
因内存和应用程序都受到管理,所以很必要增强类型安全,以确保应用的稳定性。
对于C++程序员,异常处理的切不是新的东西,但它是C#的主要功能。C#的异常处理
与C++的不同点在于它是交叉语言的(运行时的另一个功能)。在没有C#之前,您必须
处理怪异的HRESULTs,但现在由于使用了基于异常的健壮的出错处理, 这一切都 结束了。
对于现代的应用程序,安全是首要的,C#也不会例外。它提供了元数据语法,用于
声明下述NGWS安全模式的能力和许可。元数据是NGWS运行时的一个关键的概念,
下一章将涉及到它更深的含义。
面向对象
您不会预料一种新语言不支持面向对象的功能吧? C#当然支持所有关键的面向对象的概念,
如封装、继承和多态性。完整的C#类模式构建在NGWS运行时的虚拟对象系统(VOS,
Virtual Object System)的上层,VOS将在下章描述。对象模式只是基础的一部分,
不再是编程语言的一部分。
您一开始必须关注的事,就是不再有全局函数、变量或者是常量。所有的东西都封装
在类中,包括事例成员(通过类的事例--对象可以访问)或都静态成员(通过数据类型)。
这些使C#代码更加易读且有助于减少潜在的命名冲突。
定义类中的 方法默认是非虚拟的(它们不能被派生类改写)。主要论点是,这样会消除
由于偶尔改写方法而导致另外一些原码出错。要改写方法,必须具有显式的虚拟标志。
这种行为不但缩减速了虚拟函数表,而且还确保正确版本的控制。
使用C++编写类,您可以使用访问权限(access modifiers) 给类成员设置不同的访问
等级。C#同样支持private、protected 和public 三种访问权限 ,而且还增加了第四种
:internal。有关访问权限 的详细情况将在第五章 "类" 中说明。
您曾经创建了多少个类是从多基类派生出来的(ATL 程序员,您的投票不计在内!) ?
大多数情况,仅需从一个类派生出。多基类惹出的麻烦通常比它们解决的问题还多。
那就是为什么C#仅允许一个基类。如果您觉得需要多重继承,可以运用接口。
一个可能出现的问题:在C#中不存在指针,如何模仿它? 这个问题的答案很有代表性,
它提供了对NGWS运行时事件模式的支持。再次,我将把对它的全面解释放到第五章。
类型安全
我再次选指针作为一个例子。在C++中拥有一个指针,您能自由地把它强制转换成为
任何类型,包括干出诸如把一个int*(整型指针)强制转换成一个double *(双精度指针)
这样的傻事。只要内存支持这种操作,它就"干过"。这并不是您所想象的企业级编程语言
的类型安全。
纲要性的问题,,C#实施最严格的类型安全,以保护自己及垃圾收集器(garbage
collector)。所以必须遵守C#中一些相关变量的规则:
您 不能使用没有初始化的变量。对于对象的成员变量,编译器负责清零。而局部变量,
则由您负责清零。当您使用一个没有初始化的变量时,编译器会教您怎么做。
优点是能够避免由于使用不经初始化的变量计算结果而导致的错误,而您还不知道
这些奇怪的结果是如何产生的。
C#取消了不安全的类型转换。不能把一个整型强制转换成一个引用类型(如对象),
而当向下转换时,C#验证这种转换是正确的。(也就是说,派生类真的是从向下转换
的那个类派生出来的。)
边界检查是C#的一部分。再也不会出现这种情况:当数组实际只定义了n-1个元素,
却超额地使用了n个元素。
算术运算有可能溢出终值数据类型的范围。C#允许在语句级或应用程序级检测这些运算。
在允许检测溢出的情况下,当溢出发生时将会抛出一个异常。
在C#中,被传递的引用参数是类型安全的。
版本可控(Versionable)
在过去的几年中,几乎所有的程序员都至少有一次不得不涉及到众所周知的"DLL地狱"。
该问题起因于多个应用程序都安装了相同DLL名字的不同版本。有时,老版本的应用程序
可以很好地和新版本的DLL一起工作,但是更多的时候它们会中断运行。现在的版本问题
真是令人头痛。
就象您将在第八章"用C#写组件"所看到的,NGWS runtime 将对您所写的应用程序提供版
本支持。C#可以最好地支持版本控制。尽管C#不能确保正确的版本控制,但是它可以为
程序员保证版本控制成为可能。有这种支持,一个开发人员就可以确保当他的类库升级时,
仍保留着对已存在的客户应用程序的二进制兼容。
兼容
C#并没有存在于一个封闭的世界中。它允许使用最先进的NGWS的通用语言规定(Common
Language Specification,简写为CLS)访问不同的API。CLS规定了一个标准,用于符合
这种标准的语言的内部之间的操作。为了加强CLS的编译,C#编译器检测所有的公共出口
编译,并在通不过时列出错误。
当然,您也想能够访问旧一点的COM对象。NGWS运行时提供对COM透明的访问。如何集成
原来的代码将在第10章"非管理代码的内部操作"有介绍。
OLE 自动化是一种特殊的动物。任一个使用C++创建OLE自动化项目的人已经喜欢上各种
各样的自动化数据类型。有个好消息就是C#支持它们,而没有烦锁的细节。
最后,C#允许您 用C 原型的API进持内部操作。可以从您的应用程序访问任何DLL中的
入口点(有C的原型)。用于访问原始API的功能称作平台调用服务(Plaform Invocation
Services ,缩写PInovke),第10章将展示使用C API进行内部操作的一些例子。
灵活
上一部分的最后一段有可能提醒了程序员。您可能会问:"难道就没有我要传递指针的
API吗?" 您是正确的。不是仅有少数的这种API,而是很多(有点保守的估计)。这种对
原始WIN32代码的访问有时导致对非安全类指定指针的使用(尽管它们中的一些由于受
COM和PInvoke的支持可以解决)。
尽管C#代码的缺省状态是类型安全的,但是您可以声明一些类或者仅声明类的的方法
是非安全类型的。这样的声明允许您使用指针、结构,静态地分配数组。安全码和非安全
码都运行在同一个管理空间,这样暗示着当从安全码调用非安全码时不会陷入
列集(marshaling)。
小结
C#语言从C和C++演变而来,它是给那些愿意牺牲C++一点底层功能,以获得更方便和更产
品化的企业开发人员而创造的。C#现代、简单、面向对象和类型安全。尽管它借鉴了C和
C++的许多东西,但是在一些诸如名字空间、类、方法和异常处理等特定领域,它们之间
还存在着巨大的差异。
C#为您提供了方便的功能,如垃圾收集、类型安全、版本控制,等等。仅有的"代价"就是,
代码操作默认是类型安全的,不允许指针。光是类型安全就可以搞定了。但是,如果您
需要指针,仍可以通过非安全码使用它们,而且当调用非安全码时,不能含有列集。
第二章 NGWS runtime 技术基础
既然你已经具有了C#全面的印象,我也想让你了解NGWS runtime的全貌。C#依靠由
NGWS提供的运行时;因此,有必要知道运行时如何工作,以及它背后所蕴含的概念。
所以,这一章分为两部分——它们是所有的概念和使用的基础。两部分的内容虽然
有些重叠,但它有助于加深理解正在学习的概念。
2.1 NGWS Runtime
NGWS和NGWS Runtime为你提供了一种运行时环境。该运行时管理执行代码,并提供
了使编程更容易的服务。只要你的编译器支持这种运行时,你就会从这种受管理的执行
环境中得益。
你猜测C#编译器支持NGWS runtime很正确,但是不仅它支持NGWS runtime,VB和C++
也支持。这些为支持运行时所创建的代码称作"受管代码"(managed code)。以下是你的
应用程序从NGWS runtime那里所得到的利益:
交叉语言集成(通过通用语言规范)
自动内存管理(垃圾收集)
交叉语言异常处理(统一展开)
增强安全(包括类型安全)
版本支持("DLL地狱"终结者)
组件交互简化模式
因NGWS runtime 要提供了所有的这些好处,编译器必须把元文件和受管代码一起发出。
元文件描述代码中的类型,它和你的代码存在一起(与PE类似---PE为可变位执行文件)
正如你从很多种交叉语言功能所看到的,NGWS runtime主要是关于高度集成交叉多
异编程语言(tight integration across multiple different programming languages)。
这种支持可达到允许你从一个VB对象派生出一个C#类的程度(我后面会给出要讨论的文章)。
C#程序员将会喜欢的一个功能是,他们不必担心内存管理—也就是说不必担心臭名昭著
的内存泄漏。NGWS runtime提供了内存管理,当对象和变量的生命期结束(不再被引用)
时,垃圾收集器释放它们。我真的喜欢这个功能,因为在COM中的内存管理一直是我的一块
心病。
应该鼓励配置一个管理应用程序或者组件。因为管理应用程序含有元数据文件,
NGWS runtime可以利用这些信息,以确保你的应用程序具有它所需的各种规定版本。
所产生的明显效果为,由于你的代码没有相互之间的依赖,很少可能出现中断。
这章余下来的将分为两部分,每一部分讨论NGWS runtime的各个方面,直到你的
C#应用程序能执行为止。
1、中间语言(Intermediate Language,缩写IL)和元数据
2、即时编译器(just-in-time compliers,简称JITers)
2.1.1 中间语言和元数据
由C#编译器生成的受管代码并不是原始代码,但它是中间语言(IL)代码。
这种IL代码自身变成了NGWS runtime的受管执行进程的入口。IL代码明显的优势
在于它是CPU无关的,这也意味着,你要用目标机器上的一个编译器才能把IL代码
转换成原始代码。
尽管IL代码由编译器产生,但它并不是编译器提供给运行时仅有的东西。
编译器同样产生有关你代码的元数据,它告诉运行时有关你代码的更多的东西,
例如各种类型的定义、各种类型成员的签名以及其它数据。基本上,元数据是类型库、
注册表内容和其它用于COM的信息。尽管如此,元数据还是直接和执行代码合并在一起,
并不处在隔离的位置。
IL和元数据存放于扩展了PE格式的文件中(PE格式用于.exe和.dll文件)。
当这样的一个PE文件被装载时,运行时从文件中定位和分离出元数据和IL。
在进一步说明之前,我想给你已有的IL指令的简短目录。尽管它不是一个
完整的清单,也不需要你熟记和理解,但是它列出了你所必需的、C#程序所基于的
知识基础。
算术和逻辑操作符
控制流
直接内存访问
堆栈操作
参数和局部变量
堆栈分配
对象模式
实例类型值
临界区
数组
分型位置
即时编译器(JITters)
2.1.2 即时编译器(JITters)
由C#或其它能产生受管代码的编译器所生成的受管代码就是IL码。虽然IL代码被
包装在一个有效的PE文件中,但是你还是不能执行它,除非它被转换成为受管原始代码。
这就是NGWS runtime 即时编译器(也称作JITters)大显身手的时候。
为什么你会对即时编译代码感到厌繁, 为什么不把整个IL PE文件编译成原始代码?
答案是时间——需要把IL代码编译成CPU规格的代码的时间。这种编译将更加有效率,
因为一些程序段从来就没有被执行过。例如,在我的字处理器中,邮件合并功能从来就
没有被编译。
从技术上说,全部的处理过程如下:当一个类型被装载时,装载器创建一个存根
(stub),并使它连接每一个类型的方法。当一个方法第一次被调用时,存根把控制交给
JIT。JIT把IL编译为原始代码,且把存根指针指向缓冲了的原始代码。接着的调用将
执行原始码。在某些位置上(At some point),所有的IL都被转换成为原始代码,
而JITter处于空闲状态。
正如我在前面提到的,JIT编译器有很多,不止一个。在Windows平台上,
NGWS runtime装有3个不同的JIT编译器。
JIT——这是NGWS runtime默认使用的JIT编译器。它是一个后台(back end)优化
的编译器 ,在前台(up front)实行数据流分析,并创建了高度优化的受管原始代码做为
输出结果。JIT可以使用不严格的IL指令集编码,但是所需资源将十分可观。主要的限制
在于内存足迹(footprint)、结果工作集,以及实行优化所消耗的时间。
EconoJIT—— 和主JIT相比,EconJIT的目标是把IL高速地转换成受管原始代码。
它允许缓冲所产生的原始代码,但是输出码并不象主JIT生成的代码那样优化(代码小)。
当内存紧张时,快速代码生成方案的优势将荡然无存。通过永久地抛弃无用的已JIT过
的代码,你可以把更大的IL程序装入代码缓冲区。因为JIT编译快,执行速度也仍然很快。
PreJIT—尽管它是基于主JIT的,但操作起来更象是一个传 统的编译器。你安装了
NGWS组件,它才能运行,才可以把IL代码编译成受管原始代码。当然最终的结果为,
更快的装载时间和更快的应用程序启动时间(不需要更多的JIT编译)。
在所列出的JITters中,有两个是运行时的JITters。可是你怎么决定要使用哪一个
JIT,它如何使用内存? 有一个称做"JIT编译管理器"的小应用程序(jitman.exe),
它存放于NGWS SDK安装目录下的bin目录中。当执行该程序时,它把一个图标加到系统
任务条上,双击该图标打开程序对话框(见图2.1)。
图2.1 JIT编译管理器允许你设置各种相关性能的选项
尽管它是一个小小的对话框,可是你所选择的选项功能是相当强大的。每一个选项
将在以下描述。
Use EconoJIT only 选项——当该复选框没有选上时,NGWS runtime使用默认的正
常的JIT编译器。前面就曾经解释过两种JITter的区别。
Max Code Pitch Overhead(%)选项——该设置仅保留给EconoJIT。它控制了JIT编译
时间和执行代码时间的百分比。如果超过了设定的域值,代码缓冲区得到扩充,以缩短
JIT编译所消耗的时间。
Limit Size of Code Cache选项——该项默认为非选。没有选择该项意味着缓冲区将
使用它所能得到的内存。如果你想限制缓冲区大小,复选该选项,这将允许你使用Max
Size of Cache(bytes)选项。
Max Size of Cache(bytes)选项—控制容纳JIT代码的缓冲区的最大值。虽然你
可以非常严格地限制这个值,但你还是应该小心,不能超过这个缓冲区所适合的最大值。
否则该方法的JIT编译将会失败。
Optimize For Size选项——告诉JIT 编译器,优化的目的是为了使代码更小而不是
能执行得更快。这个设置默认是关掉的。
Enable Concurrent GC[garbage collection]选 项——垃圾收集(GC)默认地运行
在用户代码的线程中。意味GC发生时,可能会注意到回应有轻微的延迟。为防止出现该
现象,打开当前GC。注意,当前GC比标准GC更慢,它仅在windows 2000上写时(the time
of writing)有效。
当用C#创建项目时,你可能使用不同的设置试验过。当创建 UI-intensive应用
程序时,你将会看到允许当前GC的最大差别。
2.2 虚拟对象系统(VOS)
到目前为止,你仅看到了NGWS runtime如何工作,但是并不了解它工作的技术背景
以及为什么它要这样工作。这节都是关于 NGWS 虚拟对象系统的(VOS)。
以下为在VOS中形成声明、使用和管理类型模型时,NGWS runtime的规则。在VOS背后
的思想是建立一个框架,在执行代码时不能牺牲性能,允许交叉语言集成和类型安全。
我提到的框架是运行时架构的基础。为了帮助你更好地了解它,我将它勾出四个区域。
当开发C#应用程序和组件时,理解它们很重要。
VOS类型系统——提供丰富的类型系统,它打算支持全面编程语言的完全实施。
元数据——描述和引用VOS类型系统所定义的类型。元数据的永久格式与编程语言
无关,但是,元数据拿自己当作一种互换机制(nterchange mechanism)来使用,
这种互换是在在工具和NGWS的虚拟执行系统之间。
通用语言规范(CLS)——CLS定义了VOS中类型的子集,也定义了常规的用法。
如果一个类库遵守CLS的规则,它确保类库可以在其它所有能实现CLS的编程语言上使用。
虚拟执行系统(VES)——这是VOS实时的实现。VES负责装入和执行为NGWS运得时
编写的程序。
这四个部分一起组成了NGWS runtime架构。每一部分在下面小节中描述。
2.2.1 VOS类型系统
VOS类型系统提供丰富的类型系统,它打算支持多种编程语言的完全实施。所以,
VOS必须都支持面向对象的语言和过程编程语言。
现在,存在着很多种近似但有点不兼容的类型。就拿整型当例子,在VB中,它是
16位长,而在C++中,它是32位。还有更多的例子,特别是用在日期和时间以及数据库
方面的数据类型。这种不兼容使应用程序的创建和维护不必要地复杂化,尤其当程序
使用了多种编程语言时。
另一个问题是,因为编程语言之间存在着一些差别,你不能在一种语言中重用
另一种语言创建的类型。(COM用二进制标准接口部分地解决了这个问题)。 当今代码
重用肯定是有限的。
发布应用程序的最大障碍是各种编程语言的对象模型不统一。几乎每一方面都
存在着差异:事件、属性、永久保存(persistence)等等。
VOS这里将改变 这种现象 。VOS定义了描述值的类型,并规定了类型的所有值
所必须支持的一条合约。由于前面提到的支持面向对象和过程编程语言,就存在着
两种值和对象。
对于值,类型存储于表述(representation)中,同样操作也在其中实行。对象
更强大因为它显式地存于表述中。每一个对象都有一个区别于其它对象的识别号。
支持不同的VOS类型在第四章 "C#类型"中提出。
2.2.2元数据
尽管元数据用于描述和引用由VOS类型系统定义的类型,但它还不能锁定到这个单个
目标。当你写一个程序时,通过利用类型声明,你所声明的类型(假定它们是数值类型或
引用类型)被介绍给NGWS runtime类型系统。类型声明在存于PE可执行文件内部的元数据
中得到描述。
基本上,元数据用于各项任务:用于表示NGWS runtime用途的信息,如定位和装载类、
内存中这些类的事例、解决调用 、翻译IL为原始码、加强安全并设置运行时上下文边界
。
你不必关心元数据的生成。元数据是由C#的"代码转IL编译器"(code-to-IL compiler,
不是JIT编译器)生成的。代码转IL编译器发送二进制元数据信息给PE文件,是以标准的方
式发送的,不象C++编译器那样,为出口函数创建它们自己的修饰名字。
你从元数据和可执行代码并存所获得的主要优势为,有关类型的信息同类型自身固定
在一起,不会遍布很多地方。同样有助于解决存在于COM中的版本问题。进一步地,你
可以在相同的上下文中使用不同的版本库,因为库不仅被注册表引用,也被包含在可执行
代码中的元数据引用。
2.2.3通用语言规范
通用语言规范(CLS)并不是虚拟对象系统(VOS)真正的一部分,它是特殊的。CLS定义了
VOS中的一个类型子集,也定义了必须符合CLS的常规用法。
那么,对此有什么迷惑呢?如果一个类库遵守CLS规则,其它编程语言同样也遵守
CLS规则,那么其它编程语言的客户也可以使用类库。CLS是关于语言的交互可操作性
(interoperability)。因此,常规用法必须仅遵循外部可访问项目 (externally visible
items)如方法、属性和事件等等。
我所描述的优点是你可以做以下工作。用C#写一个组件,在VB中派生它,因加在
VB中的功能是如此之强大,在C#中再次从VB类派生它。只要所有的外部可访问项遵守
CLS规则,这样是可行的。
我在这本书中出示的代码不关心CLS协定。但在构建你的类库时要注意到CLS协定。
我提供了表2.1,用以给类型和外部可访问项定义协定规则。
这个清单不完整。它仅包含一些很重要的项目。我不指出出现在本书中每一种
类型的CLS协定,所以有个好主意:当你寻找CLS协定时,至少应该用浏览该表,以
了解哪种功能有效。不要担心你不熟悉这章表中的每一个含义,在这本书中你会学到
它们。
表2.1 通能语言规范中的类型和功能
bool
char
byte
short
int
long
float
do
uble
string
object(所有对象之母)
Arrays(数组)
数组的维数必须是已知的(>=1),而且最小下标数必须为0。
要素类型必须是一个CLS类型。
类型(Types)
可以被抽象或隐藏。
零或更多的接口可以被实现。不同的接口允许拥有具有相同名字和签名的方法。
一个类型可以准确地从一个类型派生。允许成员被覆盖和被隐藏。
可以有零或更多的成员,它们是字段(fields)、方法、事件或者类型。
类型可以拥有零或更多个构造函数。
一种类型的可访问性可以是公共的或者对NGWS组件来说是局部的;但是,
仅公共成员可以认为是类型接口的一部分。
所有的值型必须从系统值型继承。异常是一个枚举——它必须从系统枚举
(System Enum)继承。
类型成员
类型成员允许隐藏或者覆盖另一种类型中的其它成员。
参数和返回值的类型都必须是 CLS 协定 类型。
构造函数、方法和属性可以被重载。
一个类型可以有抽象成员,但仅当类型不被封装时。
方法
一种方法可以是静态、虚拟或者实例。
虚拟和实例方法可以是抽象的,或者是一个实现。静态方法必须总拥有一个实现。
虚拟方法可能是最后的(或者不是)。
字段(Fields)
可以是静态或者是非静态。
静态字段可以被描述或只初始化。
属性
当获取和设置方法而不是使用属性语法时,属性可以公开。
获取的返回类型和设置方法的第一个参数必须是相同的CLS类型——属性的类型。
属性名字必须不同,不同的属性类型用于区分是不充分的。
由于使用方法实现属性访问,如果 PropertyName 是同一个类中定义的一个属性,
你不能实现命名为 get_PropertyName 和 set_PropertyName 的方法。
属性可以被索引。
属性访问必须遵循这种命名格式:get_ProName,set_PropName。
枚举(Enumerations)
强调类型必须是byte、short、int 或long。
每一个成员是一个枚举类型的静态描述字段。
一个枚举不能实现任何接口。
你允许给多字段设定相同的值。
一个枚举必须继承系统枚举(隐含在C#中)
异常
可以被引发和被捕获。
自定义异常必须继承系统异常。
接口
可需要实现其它接口。
一个接口可以定义属性、事件和虚拟方法。实现取决于派生类。
事件
增加和取消方法必须是都提供或者都没有 ,每一种方法采用一个参数,
它是一个从系统代表元(System Delegate)派生下来的类。
自定义属性
可以仅使用下更类型:Type(类型),char, char, bool, byte, short, int, long,
float,do
uble, enum (一种CLS 类型), and object.
代表元(Delegates)
可以被创建和被激活
标识符(Identifiers)
一个标识符的第一个字母必须来自一限制集。
通过大小写在单一范围内,不可能唯一地区别两个或更多个标识符(大小写不敏感)。
2.2.4虚拟执行系统(VES)
虚拟执行系统实现了虚拟对象系统。通过实现一个负责NGWS runtime的执行引擎
(execution engine,缩写EE)创建VES。这个执行引擎执行你用C#编写和编译的应用程序。
下列组件为VES的一部分。
1、中间语言(IL)——被设计为很容易受各种各样的编译器所兼容 。在该框架之外,
C++、VB和C#编译器都能够生成IL。
2、装入受管代码——这包括解决内存中的名字、 表层类(laying out classes ),
并且创建JIT编译所必需的存根。通过执行经常性校验,包括加强一些访问规则,
类装载器同样也增强了安全性。
3、用JIT转换IL成原始代码——IL代码并不是设计成为一种传统的解释字节代码或
树型代码,IL转换是真正的编译。
4、装入元数据、校验类型安全和方法的完整性
5、垃圾收集(GC)和异常处理——两者都是基于堆栈格式的服务。受管代码允许
你动态地跟踪堆栈。要动态地识别各个堆栈框架,JITter或其它编译器必须提供一个代码
管理器。
6、描绘和查错服务——两者都取决于由源语言编译器所生成的信息。必须发出
两个映射:一个映射从源语言结构发到指令流中的地址,一个映射从地址发到堆栈框架
中的位置。当执行从IL到原始代码的转换时,这些映射被重新计算。
7、管理线程和上下文,还有远程管理——VES为受管代码提供这些服务。
虽然这个清单并不完整,但它足以让你理解运行时基于的由VES提供的低层架构。
肯定将会有专门讨论运行时的书,而这本书将稍为深入地挖掘各种话题。
2.3 小结
这一章,我带你逛了一回运行时的世界。我描述了当创建、编译和配置C#程序时
它是如何工作的。你学会了中间语言(IL),还有元数据是如何用于描述被编译为IL的类型。
元数据和IL都用于JITter检测和执行你的代码。你甚至可以选择用哪一种JITter来执行
应用程序。
在这一章的第二部分,涉及到了运行时为何按这种方式工作的理论。你学了
虚拟对象系统(VOS)和组成它的那部分。对于类库设计者最为感兴趣的就是通用语言
规范(CLS),它为基于VOS的语言交互操作设定规则。最后,你看到了虚拟执行系统
(VES)如何通过NGWS runtime实现VOS。
展现 C#
前言
0.1 提要
欢迎阅读《展现 C#》(Presenting C#)。这本书是你提高企业编程语言的一条捷径。
这种企业编程语言带有下一代编程语言服务运行时(NGWS Runtime):C#(发音"C sharp")。
NGWS Runtime 是一个不仅管理执行代码、同时也提供使编程更加容易的动态环境。
编译器产生受管代码以指向这种受管理执行环境。你获得跨平台语言集成、跨平台语言
异常处理、增强安全性、版本控制、安排支持和查错服务。
支持NGWS Runtime 的主要语言是C#。支持NGWS框架的很多程序是用C#写的,因此,
在一些带有NGWS Runtime的编译器中,它的编译器可以被认为是非常经得起测试且是
经过优化的。C#语言借鉴了C++,但是具备现代化和新增的类型安全——使C#成为企业
解决方案的首选语言。
0.2 谁应该读这本书
如果你对编程很陌生,这本书不适合你。这本书企图让程序员解脱并使用C#,
基于他们已经拥有的知识。《展现 C#》的目标瞄准了已经具有如C/C++、VB、Java
或其它编程经验的程序员。
如果你具有C++的背景,转向C#会很容易,但是,如果你精通于其它不同的语言,
这本书也会耗掉你的一些日子。如果你有一点COM编程的知识,这本会更有趣,
但会COM编程不再是必要的。
0.3 这本书如何组织
第一章 C#简介——这一章把你带到C#逛一回,同时回答了有关你应该考虑
学习C#的问题。
第二章 NGWS Runtime 的技术基础——介绍NGWS Runtime为运行的C#代码所
提供的低层架构。
第三章 第一个C#应用程序——创建真正第一个C#应用程序(否则它是什么呢?),
它就是一个"Hello World"应用程序。
第四章 C#类型——可以看到用在C#应用程序中的各种类型。探索值型和引用型
的区别,还有如何加框和消框。
第五章 类——C#功能真正强大的标志,它是具有类的面向对象的编程。你会学
到好多好多的东西,如构造函数、析构函数、方法、属性、索引和事件等。
第六章 控制语句——对应用程序实行流程控制。探索C#提供的各种选择和陈述
语句。
第七章 异常处理——获得了编写实现异常处理的应用程序技巧,拥有这种技巧,
在NGWS Runtime世界,你是一个好的公民
第八章 用C#写组件——用C#构建组件,因为支持NGWS Runtime,它可以被不同
语言平台的客户使用。
第九章 配置和安排——学到如何用C#有条件地进行编译工作,和如何为你的C#
源码自动地创建文档。另外,这一章将介绍到NGWS的版本技术问题。
第十章 用非受管代码交互操作——发现你如何使用C#内部的非受管代码,且非
受管代码如何与你的C#组件一起交互操作。
第十一章 C#代码查错——获得使用SDK查错工具的技巧,可以在应用程序中准确
地找到错误并修改它们。
第十二章 安全——探索NGWS Runtime的安全概念。学到代码访问安全性和基于
角色的安全性。
0.4 使用这本书你需要什么?
从这本书的观点看,你所需要的就是下一代windows服务软件开发包(NGWS SDK)。
尽管至少只要有NGWS Runtime 和C#编译器就可以,但当探索这些激动人心的新技术功能
时,在一台机器上装有说明文件档和所有的SDK工具(包括debugger),将是一个极好的主意。
这本书不需要你装在机器上的Visual Studio 7的任何工具。我仅建议你要有一个
优秀的支持行数的程序编辑器,用以编辑C#源文件。
……
(省略掉一些有关出版过程和表示感谢的段落)
0.5 关于作者
Christoph Wille,微软系统工程师(MCSE)、微软系统开发师(MCSD)、Netware网络
管理员(CNA)和MCP-IT,作为一个网络咨询人员和程序员,特别精通Windows DNA。微软
认为他是ASP方面最有价值的人(MVP),他是和微软在一起使用早期的C#版本的少数开发者
之一。
Christoph 独自或参与了几本书的写作,包括Sams出版社的《自学ADO2.5 21天》、
《自学ASP 24小时》、《自学MCSE TCP/IP 14天》和《MCSE 训练指南:SQL Server 7
管理》。
第三章 第一个C#应用程序
3.0 选择一个编辑器
尽管我是一个顽固的Notepad狂,但这次我不建议用它编辑源码。原因是
你正在与真正的编程语言打交道,使用Notepad编辑源码编译时可能产生大量的错误
信息行(C++程序员知道我在说什么。)
你有几种选择。可以重新配置你信任的老式Visual C++ 6.0,使它能够和C#
源文件一起工作。第二种选择是使用新的Visual Studio 7。第三,你可以用任何第三方
程序编辑器,最好要支持行数、色彩编码、工具集成和良好的搜索功能。CodeWright就是
其中一个例子,如图3.1所示。
图3.1 CodeWright 是你可以用于创建C#代码文件众多可能编辑器中的一个。
当然,在所提到的编辑器中,没有一个对创建C#程序来说是必要的。用Notepad
肯定可以编辑。但是,如果你考虑到要编写更大的项目,最好还是忍痛割爱吧。
3.1 "Hello World" 代码
讨论编辑器有点离题 ,让我们把话题转回到一个非常出名的小应用程序。这个
最短的C#版本应用程序见清单3.1。把它存起来,文件名为 helloworld.cs,以便使你能按
照说明,完成诸如编译应用程序等其它余下来的步骤。
清单 3.1 最简单的 "Hello World "程序
1: class HelloWorld
2: {
3: public static void Main()
4: {
5: System.Console.WriteLine("Hello World");
6: }
7: }
在C#中,代码块(语句组)由大括弧({和})所括住。所以,甚至你以前没有C++的
经验,你也可以说出Main()方法就是HelloWorld 类语句的一部分,因为类被括在所定义
的大括弧中。
C#应用程序(可执行)的入口点就是 static Main 方法,它必须包含在一个类中。
仅有一个类能使用该标志定义,除非你告诉编译器它应使用哪一个 Main 方法(否侧,
在这个方法中,你的程序开始并结束。方法中可以调用其它方法——如这个例子中,
用于输出文本——或者创建对象并激活该方法。
正如你所看到的,Main方法返回一个void类型。
public static void Main()
尽管看到这些语句时,C++程序员肯定会觉得似曾相识,但是其他程序员并不如此。首先,public 的访问标志告诉我们这个方法可以被任何程序访问,这是它被调用的必要条件。其次,static 意味着没有先创建类的实例也可以调用方法——你所要做的就是用类名调用方法。
HelloWorld.Main();
但是,我不赞成在Main方法中执行这行代码,递归会导致堆栈溢出。
另一重要的方面是返回类型。对于方法Main,可选择void (意味着根本就
没有返回值),或用int 为整型结果(应用程序返回的错误级别)。因此,两种可能的Main
方法为:
public static void Main()
public static int Main()
C++程序员会同样知道后面我要提到的——可以传给应用程序的命令行参数数组。
如:
public static void Main(string[] args)
我现在并不想详细地说明如何访问参数,但我想事先给C++程序员一个警告:
和C++相比,应用程序路径不是这个数组的一部分。仅仅那些参数包含在这个数组中。
在对Main方法并不简短的介绍之后,让我们把注意力集中到唯一真正的代码行
——这行代码在屏幕上显示"Hello Wold"。
System.Console.WriteLine("Hello World");
假如不是由于有了System,大家会马上猜到WriteLine是Console 对象的一个
静态方法。那么System代表什么呢? 它是包含Console对象的名字空间(范围),实际上
并不是每次都在Console对象前加上名字空间的前缀,你可以象清单3.2所示范的那样,
应用程序中引入名字空间。
清单3.2 在应用程序中引入名字空间
1: using System;
2:
3: class HelloWorld
4: {
5: public static void Main()
6: {
7: Console.WriteLine("Hello World");
8: }
9: }
所有你要做的就是给System名字空间加一个using指令。在这之后,不再需要
规定名字空间,就可以使用它们的方法和属性了。NGWS 框架体系中有很多的名字空间,
我只对巨大的名字空间池中的少数几个对象进行探讨。但在第八章 "用C#写组件"将介绍
为你的对象创建自己的名字空间。
3.2 编译应用程序
由于NGWS Runtime支持所有的编译器(VB、C++和C#),你不必买一个单独的
开发工具用来把应用程序编译成IL(中间语言)。但是,如果你从没有用过命令行编译器
编译过应用程序(仅懂得编译名,而没有熟记), 它还是你的首要选择。
打开命令提示符并切换到存 helloworld.cs 的目录。敲入以下命令:
csc helloworld.cs
helloworld.cs 被编译并链接成hellworld.exe。因为源码没有错误(那当然!),
C#编译器没有出错提示,在整个编译过程没有丝毫停顿。如图3.2所示。
图3.2 使用命令行编译器 csc.exe 编译应用程序
现在你已经准备好运行第一个真正用C#编写的应用程序。简单地在命令行上
敲入helloworld,输出结果为 "Hello World"。
在继续往下介绍之前, 我想稍为想象一下第一个应用程序和一个编译器开关的
使用:
csc /out:hello.exe helloworld.cs
这个开关告诉编译器输出文件命名为hello.exe。虽然这不是什么绝招,但它是
这本书中用到的未来编译器的基本功。
3.3 输入和输出
到目前为止,我仅仅演示了把简单的常量字符串输出到屏幕。尽管这本书只
介绍了C#编程的概念而不介绍用户接口编程,但我需要让你迅速学会简单的屏幕输入和
输出方法——相应于C的scanf 和 printf,或者C++的cin 和cout。我不能提供VB相应的
函数,因为屏幕访问不是该核心语言的一部分。
你只需要能够读用户的输入并提示一些信息给用户。清单3.3 说明如何读
一个用户请求的名字输入,并显示一条已定制好的"Hello" 信息。
Listing 3.3 从控制台读输入信息
1: using System;
2:
3: class InputOutput
4: {
5: public static void Main()
6: {
7: Console.Write("Please enter your name: ");
8: string strName = Console.ReadLine();
9: Console.WriteLine("Hello " + strName);
10: }
11: }
第7行使用Console对象的一个新方法用于提示文本信息给用户,它就是Write方法。
它与WriteLine不同的地方在于它输出时不换行。我使用这种方法以便用户可以在信息提示
的同一行输入名字。
在用户输入他的名字后(并按回车键),ReadLine 方法读入了一个字符串变量。
名字字符串连接到常量字符串"Hello",并用我们早已熟悉的WriteLine方法显示出来
(见图3.2)。
图3.3 编译和运行定制的Hello 应用程序
你几乎已学完了NGWS框架必要的输入和输出功能。但是,你还需要为用户显示
多个值。为用户写一个格式串。清单3.4展示一个例子。
清单 3.4 使用不同的输出方法
1: using System;
2:
3: class InputOutput
4: {
5: public static void Main()
6: {
7: Console.Write("Please enter your name: ");
8: string strName = Console.ReadLine();
9: Console.WriteLine("Hello {0}",strName);
10: }
11: }
第9行包含了使用格式串的Console.WriteLine语句。格式串例子如下:
"Hello {0}"
{0}代替WriteLine方法的参数表中紧随格式串后的第一个变量。你可以用该
技术格式化超过三个变量。
Console.WriteLine("Hello {0} {1}, from {2}",
strFirstname, strLastname, strCity);
当然,并不仅限于只使用字符串变量。你可以使用任何类型,这些类型在
后面的第四章 "C#类型"中有讨论。
3.4 添加注释
当写代码时,你应为代码写注释条文,解释实现的内容、变更史等。
尽管你注释中提供的信息(如果有的话)是给你写的,但是你还是必须遵守写C#注释
的方法。清单3.5 显示采用的两种不同的方式。
清单3.5 给你的代码添加注释
1: using System;
2:
3: class HelloWorld
4: {
5: public static void Main()
6: {
7: // 这是单行注释
8: /* 这种注释
9: 跨越多行 */
10: Console.WriteLine(/*"Hello World"*/);
11: }
12: }
"//" 符号用于单行注释。你可以用"//"注释当前所在行,或是跟在一个代码
语句的后面:
int nMyVar = 10;
// 胡说八道
所有在"//"后面的被认为是一条注释;所以,你可以同样用它们来注释一整行
或一行源代码的部分。这种注释方式同C++中介绍的相似。
如果你的注释跨越多行,必须使用"/* */"的字符组合。这种方式在C中有效。
除了单行注释外,这种方式在C++和C#中还同样有效。因C/C++和C#都使用这种多行注释
方式,所以它们也使用相同的终结符。请看下列代码行:
/* Console.WriteLine("Hello World");
*/
我使用"/* */"简单地注释一整行。现在我假定这一行是很长代码的一部分,
而且我决定要暂时禁用一个程序块:
/*
...
/* Console.WriteLine("Hello World");
*/
...
*/
这个结构所存在的问题为: "Hello World"那一行后面的"*/"终止了始于
第一行的"/*"的注释,余下的代码对编译器有效,你将看到一些有趣的出错信息。
至少 最后的"*/"被标志为归属错误。我只不过想提醒一下,让你了解这种错误。
3.5小结
在这一章中,你创建、编译并执行了第一个C#应用程序:
著名的"Hello World"程序。我用这个短短的应用程序给你介绍有关Main方法,
它是一个应用程序的入口点,也是出口点。这个方法可以没有返回值或返回一个
整数错误级别。如果你的应用程序用参数调用,你可以(但不必要)读出并使用它们。
在编译和测试应用程序后,你学到了更多的由Console对象提供的有关输入
和输出的方法。对于学习C#而言,它们足以创建出有意义的控制台例子,但用户接口
的大部分将是WFC、WinForms或者ASP+。
第一章 C# 简介
欢迎您加入C#的世界! 这一章将把您引进C#的天地,并回答一些相关的问题,如:
您为什么要使用C#,C++和C#的主要有什么不同点,以及为什么C#使开发更容易而且
还使您感到很有趣。
为什么是另外一种编程语言?
必须回答的一个问题:当您已经使用C++或VB从事企业开发时,为什么还要学习
另一种语言? 市场式的回答就是:"在企业计算领域,C#将会变成为用于编写
"下一代窗口服务"(Next Generation Windows Services,简写为NGWS )应用程序
的主要语言。" 这一章将对用参数请求提供支持,并陈列了C#的一些功能。
这一章会使您开胃的。
C#语言自C/C++演变而来。但是,它现代、简单、完全面向对象和类型安全。
如果您是C/C++程序员,学习曲线将会很平坦。许多C#语句直接借用您所喜爱的语言,
包括表达式和操作符。假如不仔细看,简直会把它当成C++。
关于C#最重要的一点:它是现代的编程语言。它简化和现代化了C++在类、
名字空间、方法重载和异常处理等领域。屏弃了C++的复杂性,使它更易用、
更少出错。
对C#的易用有贡献的是减少了C++的一些特性,不再有宏、模板和多重继承。
特别对企业开发者来说,上述功能只会产生更多的麻烦而不是效益。
使编程更方便的新功能是严格的类型安全、版本控制、垃圾收集(garbage
collect)等等。所有的这些功能的目标都是瞄准了开发面向组件的软件。
在继续呈现出更多的功能之前,我想停下来并在下面说明C#至关重要的各种要素。
简单
现代
面向对象
类型安全
版本控制
兼容
灵活
简单
C#具有C++所没有的一 个优势就是学习简单。该语言首要的目标就是简单。
很多功能(还不如说是缺少了C++的一些功能)有助于C#全方位的简单。
在C#中,没有C++中流行的指针。默认地,您工作在受管理的代码中,
在那里不允许如直接存取内存等不安全的操作。我想没有C++程序员可以声称,
从没有使用指针访问过不属于他们的内存。
与指针"戏剧性"密切相关的是"愚蠢的"操作。在C++中,有::、.、和->操作符,
它们用于名字空间、成员和引用。对于新手来说,操作符至今仍是学习的一道难关。C#弃用其它操作符,仅使用单个操作符 "."。现在一个程序员所需要理解的就是嵌套名字的注解了。
您不必记住基于不同处理器架构的隐含的类型,甚至各种整型的变化范围。
C#使用统一的类型系统,屏弃了C++多变的类型系统。这种系统充许您把各种类型
作为一个对象查看,它是一个原始类型还是一个full-blown 类。和其它编程语言相比,由于加框(boxing)和消框(unboxing)的机制,把简单类型当作对象处理并不能获得性能的改善。稍后将详细解释加框和消框,但基本上仅当需要时才使用对象访问简单类型这种技术。
首先,老练的程序员可能不喜欢它,但是整型和布尔型如今终归是两种完全不同的
数据类型。这就意味着原来if语句中错误的赋值现在会被编译出错,因为if语句只
接受布尔类型的值。再也不会出现误用赋值符为比较符这样的错误!
C#同时也解决了存在于C++中已经有些年头的多余东西(redundancies)。这种多余
包括常数预定义,不同字符类型等。鉴于多余表单已经从该语言中消失,故一般在C#中
都可以使用表单了。
现代
您投入学习C#的努力是一笔大投资,因为C#是为编写NGWS 应用程序的主要语言而设计。
您 将会发现很多自己用C++可以实现或者很费力实现的功能,在C#中不过是一部分
基本的功能而已。
对于企业级的编程语言来说,新增的金融数据类型很受欢迎。您用到了一种新的
十进制数据类型,它专用于金融计算方面。如果不喜欢这种现成简单的类型,
根据您应用程序的特殊需求,可以很容易地创建出新的一种数据类型。
我已经提到,指针不再是您编程武器的一部分。不要太惊讶,全面的内存管理已经
不是您的任务。运行时NGWS提供了一个垃圾收集器,负责C#程序中的内存管理。
因内存和应用程序都受到管理,所以很必要增强类型安全,以确保应用的稳定性。
对于C++程序员,异常处理的切不是新的东西,但它是C#的主要功能。C#的异常处理
与C++的不同点在于它是交叉语言的(运行时的另一个功能)。在没有C#之前,您必须
处理怪异的HRESULTs,但现在由于使用了基于异常的健壮的出错处理, 这一切都 结束了。
对于现代的应用程序,安全是首要的,C#也不会例外。它提供了元数据语法,用于
声明下述NGWS安全模式的能力和许可。元数据是NGWS运行时的一个关键的概念,
下一章将涉及到它更深的含义。
面向对象
您不会预料一种新语言不支持面向对象的功能吧? C#当然支持所有关键的面向对象的概念,
如封装、继承和多态性。完整的C#类模式构建在NGWS运行时的虚拟对象系统(VOS,
Virtual Object System)的上层,VOS将在下章描述。对象模式只是基础的一部分,
不再是编程语言的一部分。
您一开始必须关注的事,就是不再有全局函数、变量或者是常量。所有的东西都封装
在类中,包括事例成员(通过类的事例--对象可以访问)或都静态成员(通过数据类型)。
这些使C#代码更加易读且有助于减少潜在的命名冲突。
定义类中的 方法默认是非虚拟的(它们不能被派生类改写)。主要论点是,这样会消除
由于偶尔改写方法而导致另外一些原码出错。要改写方法,必须具有显式的虚拟标志。
这种行为不但缩减速了虚拟函数表,而且还确保正确版本的控制。
使用C++编写类,您可以使用访问权限(access modifiers) 给类成员设置不同的访问
等级。C#同样支持private、protected 和public 三种访问权限 ,而且还增加了第四种
:internal。有关访问权限 的详细情况将在第五章 "类" 中说明。
您曾经创建了多少个类是从多基类派生出来的(ATL 程序员,您的投票不计在内!) ?
大多数情况,仅需从一个类派生出。多基类惹出的麻烦通常比它们解决的问题还多。
那就是为什么C#仅允许一个基类。如果您觉得需要多重继承,可以运用接口。
一个可能出现的问题:在C#中不存在指针,如何模仿它? 这个问题的答案很有代表性,
它提供了对NGWS运行时事件模式的支持。再次,我将把对它的全面解释放到第五章。
类型安全
我再次选指针作为一个例子。在C++中拥有一个指针,您能自由地把它强制转换成为
任何类型,包括干出诸如把一个int*(整型指针)强制转换成一个double *(双精度指针)
这样的傻事。只要内存支持这种操作,它就"干过"。这并不是您所想象的企业级编程语言
的类型安全。
纲要性的问题,,C#实施最严格的类型安全,以保护自己及垃圾收集器(garbage
collector)。所以必须遵守C#中一些相关变量的规则:
您 不能使用没有初始化的变量。对于对象的成员变量,编译器负责清零。而局部变量,
则由您负责清零。当您使用一个没有初始化的变量时,编译器会教您怎么做。
优点是能够避免由于使用不经初始化的变量计算结果而导致的错误,而您还不知道
这些奇怪的结果是如何产生的。
C#取消了不安全的类型转换。不能把一个整型强制转换成一个引用类型(如对象),
而当向下转换时,C#验证这种转换是正确的。(也就是说,派生类真的是从向下转换
的那个类派生出来的。)
边界检查是C#的一部分。再也不会出现这种情况:当数组实际只定义了n-1个元素,
却超额地使用了n个元素。
算术运算有可能溢出终值数据类型的范围。C#允许在语句级或应用程序级检测这些运算。
在允许检测溢出的情况下,当溢出发生时将会抛出一个异常。
在C#中,被传递的引用参数是类型安全的。
版本可控(Versionable)
在过去的几年中,几乎所有的程序员都至少有一次不得不涉及到众所周知的"DLL地狱"。
该问题起因于多个应用程序都安装了相同DLL名字的不同版本。有时,老版本的应用程序
可以很好地和新版本的DLL一起工作,但是更多的时候它们会中断运行。现在的版本问题
真是令人头痛。
就象您将在第八章"用C#写组件"所看到的,NGWS runtime 将对您所写的应用程序提供版
本支持。C#可以最好地支持版本控制。尽管C#不能确保正确的版本控制,但是它可以为
程序员保证版本控制成为可能。有这种支持,一个开发人员就可以确保当他的类库升级时,
仍保留着对已存在的客户应用程序的二进制兼容。
兼容
C#并没有存在于一个封闭的世界中。它允许使用最先进的NGWS的通用语言规定(Common
Language Specification,简写为CLS)访问不同的API。CLS规定了一个标准,用于符合
这种标准的语言的内部之间的操作。为了加强CLS的编译,C#编译器检测所有的公共出口
编译,并在通不过时列出错误。
当然,您也想能够访问旧一点的COM对象。NGWS运行时提供对COM透明的访问。如何集成
原来的代码将在第10章"非管理代码的内部操作"有介绍。
OLE 自动化是一种特殊的动物。任一个使用C++创建OLE自动化项目的人已经喜欢上各种
各样的自动化数据类型。有个好消息就是C#支持它们,而没有烦锁的细节。
最后,C#允许您 用C 原型的API进持内部操作。可以从您的应用程序访问任何DLL中的
入口点(有C的原型)。用于访问原始API的功能称作平台调用服务(Plaform Invocation
Services ,缩写PInovke),第10章将展示使用C API进行内部操作的一些例子。
灵活
上一部分的最后一段有可能提醒了程序员。您可能会问:"难道就没有我要传递指针的
API吗?" 您是正确的。不是仅有少数的这种API,而是很多(有点保守的估计)。这种对
原始WIN32代码的访问有时导致对非安全类指定指针的使用(尽管它们中的一些由于受
COM和PInvoke的支持可以解决)。
尽管C#代码的缺省状态是类型安全的,但是您可以声明一些类或者仅声明类的的方法
是非安全类型的。这样的声明允许您使用指针、结构,静态地分配数组。安全码和非安全
码都运行在同一个管理空间,这样暗示着当从安全码调用非安全码时不会陷入
列集(marshaling)。
小结
C#语言从C和C++演变而来,它是给那些愿意牺牲C++一点底层功能,以获得更方便和更产
品化的企业开发人员而创造的。C#现代、简单、面向对象和类型安全。尽管它借鉴了C和
C++的许多东西,但是在一些诸如名字空间、类、方法和异常处理等特定领域,它们之间
还存在着巨大的差异。
C#为您提供了方便的功能,如垃圾收集、类型安全、版本控制,等等。仅有的"代价"就是,
代码操作默认是类型安全的,不允许指针。光是类型安全就可以搞定了。但是,如果您
需要指针,仍可以通过非安全码使用它们,而且当调用非安全码时,不能含有列集。
第二章 NGWS runtime 技术基础
既然你已经具有了C#全面的印象,我也想让你了解NGWS runtime的全貌。C#依靠由
NGWS提供的运行时;因此,有必要知道运行时如何工作,以及它背后所蕴含的概念。
所以,这一章分为两部分——它们是所有的概念和使用的基础。两部分的内容虽然
有些重叠,但它有助于加深理解正在学习的概念。
2.1 NGWS Runtime
NGWS和NGWS Runtime为你提供了一种运行时环境。该运行时管理执行代码,并提供
了使编程更容易的服务。只要你的编译器支持这种运行时,你就会从这种受管理的执行
环境中得益。
你猜测C#编译器支持NGWS runtime很正确,但是不仅它支持NGWS runtime,VB和C++
也支持。这些为支持运行时所创建的代码称作"受管代码"(managed code)。以下是你的
应用程序从NGWS runtime那里所得到的利益:
交叉语言集成(通过通用语言规范)
自动内存管理(垃圾收集)
交叉语言异常处理(统一展开)
增强安全(包括类型安全)
版本支持("DLL地狱"终结者)
组件交互简化模式
因NGWS runtime 要提供了所有的这些好处,编译器必须把元文件和受管代码一起发出。
元文件描述代码中的类型,它和你的代码存在一起(与PE类似---PE为可变位执行文件)
正如你从很多种交叉语言功能所看到的,NGWS runtime主要是关于高度集成交叉多
异编程语言(tight integration across multiple different programming languages)。
这种支持可达到允许你从一个VB对象派生出一个C#类的程度(我后面会给出要讨论的文章)。
C#程序员将会喜欢的一个功能是,他们不必担心内存管理—也就是说不必担心臭名昭著
的内存泄漏。NGWS runtime提供了内存管理,当对象和变量的生命期结束(不再被引用)
时,垃圾收集器释放它们。我真的喜欢这个功能,因为在COM中的内存管理一直是我的一块
心病。
应该鼓励配置一个管理应用程序或者组件。因为管理应用程序含有元数据文件,
NGWS runtime可以利用这些信息,以确保你的应用程序具有它所需的各种规定版本。
所产生的明显效果为,由于你的代码没有相互之间的依赖,很少可能出现中断。
这章余下来的将分为两部分,每一部分讨论NGWS runtime的各个方面,直到你的
C#应用程序能执行为止。
1、中间语言(Intermediate Language,缩写IL)和元数据
2、即时编译器(just-in-time compliers,简称JITers)
2.1.1 中间语言和元数据
由C#编译器生成的受管代码并不是原始代码,但它是中间语言(IL)代码。
这种IL代码自身变成了NGWS runtime的受管执行进程的入口。IL代码明显的优势
在于它是CPU无关的,这也意味着,你要用目标机器上的一个编译器才能把IL代码
转换成原始代码。
尽管IL代码由编译器产生,但它并不是编译器提供给运行时仅有的东西。
编译器同样产生有关你代码的元数据,它告诉运行时有关你代码的更多的东西,
例如各种类型的定义、各种类型成员的签名以及其它数据。基本上,元数据是类型库、
注册表内容和其它用于COM的信息。尽管如此,元数据还是直接和执行代码合并在一起,
并不处在隔离的位置。
IL和元数据存放于扩展了PE格式的文件中(PE格式用于.exe和.dll文件)。
当这样的一个PE文件被装载时,运行时从文件中定位和分离出元数据和IL。
在进一步说明之前,我想给你已有的IL指令的简短目录。尽管它不是一个
完整的清单,也不需要你熟记和理解,但是它列出了你所必需的、C#程序所基于的
知识基础。
算术和逻辑操作符
控制流
直接内存访问
堆栈操作
参数和局部变量
堆栈分配
对象模式
实例类型值
临界区
数组
分型位置
即时编译器(JITters)
2.1.2 即时编译器(JITters)
由C#或其它能产生受管代码的编译器所生成的受管代码就是IL码。虽然IL代码被
包装在一个有效的PE文件中,但是你还是不能执行它,除非它被转换成为受管原始代码。
这就是NGWS runtime 即时编译器(也称作JITters)大显身手的时候。
为什么你会对即时编译代码感到厌繁, 为什么不把整个IL PE文件编译成原始代码?
答案是时间——需要把IL代码编译成CPU规格的代码的时间。这种编译将更加有效率,
因为一些程序段从来就没有被执行过。例如,在我的字处理器中,邮件合并功能从来就
没有被编译。
从技术上说,全部的处理过程如下:当一个类型被装载时,装载器创建一个存根
(stub),并使它连接每一个类型的方法。当一个方法第一次被调用时,存根把控制交给
JIT。JIT把IL编译为原始代码,且把存根指针指向缓冲了的原始代码。接着的调用将
执行原始码。在某些位置上(At some point),所有的IL都被转换成为原始代码,
而JITter处于空闲状态。
正如我在前面提到的,JIT编译器有很多,不止一个。在Windows平台上,
NGWS runtime装有3个不同的JIT编译器。
JIT——这是NGWS runtime默认使用的JIT编译器。它是一个后台(back end)优化
的编译器 ,在前台(up front)实行数据流分析,并创建了高度优化的受管原始代码做为
输出结果。JIT可以使用不严格的IL指令集编码,但是所需资源将十分可观。主要的限制
在于内存足迹(footprint)、结果工作集,以及实行优化所消耗的时间。
EconoJIT—— 和主JIT相比,EconJIT的目标是把IL高速地转换成受管原始代码。
它允许缓冲所产生的原始代码,但是输出码并不象主JIT生成的代码那样优化(代码小)。
当内存紧张时,快速代码生成方案的优势将荡然无存。通过永久地抛弃无用的已JIT过
的代码,你可以把更大的IL程序装入代码缓冲区。因为JIT编译快,执行速度也仍然很快。
PreJIT—尽管它是基于主JIT的,但操作起来更象是一个传 统的编译器。你安装了
NGWS组件,它才能运行,才可以把IL代码编译成受管原始代码。当然最终的结果为,
更快的装载时间和更快的应用程序启动时间(不需要更多的JIT编译)。
在所列出的JITters中,有两个是运行时的JITters。可是你怎么决定要使用哪一个
JIT,它如何使用内存? 有一个称做"JIT编译管理器"的小应用程序(jitman.exe),
它存放于NGWS SDK安装目录下的bin目录中。当执行该程序时,它把一个图标加到系统
任务条上,双击该图标打开程序对话框(见图2.1)。
图2.1 JIT编译管理器允许你设置各种相关性能的选项
尽管它是一个小小的对话框,可是你所选择的选项功能是相当强大的。每一个选项
将在以下描述。
Use EconoJIT only 选项——当该复选框没有选上时,NGWS runtime使用默认的正
常的JIT编译器。前面就曾经解释过两种JITter的区别。
Max Code Pitch Overhead(%)选项——该设置仅保留给EconoJIT。它控制了JIT编译
时间和执行代码时间的百分比。如果超过了设定的域值,代码缓冲区得到扩充,以缩短
JIT编译所消耗的时间。
Limit Size of Code Cache选项——该项默认为非选。没有选择该项意味着缓冲区将
使用它所能得到的内存。如果你想限制缓冲区大小,复选该选项,这将允许你使用Max
Size of Cache(bytes)选项。
Max Size of Cache(bytes)选项—控制容纳JIT代码的缓冲区的最大值。虽然你
可以非常严格地限制这个值,但你还是应该小心,不能超过这个缓冲区所适合的最大值。
否则该方法的JIT编译将会失败。
Optimize For Size选项——告诉JIT 编译器,优化的目的是为了使代码更小而不是
能执行得更快。这个设置默认是关掉的。
Enable Concurrent GC[garbage collection]选 项——垃圾收集(GC)默认地运行
在用户代码的线程中。意味GC发生时,可能会注意到回应有轻微的延迟。为防止出现该
现象,打开当前GC。注意,当前GC比标准GC更慢,它仅在windows 2000上写时(the time
of writing)有效。
当用C#创建项目时,你可能使用不同的设置试验过。当创建 UI-intensive应用
程序时,你将会看到允许当前GC的最大差别。
2.2 虚拟对象系统(VOS)
到目前为止,你仅看到了NGWS runtime如何工作,但是并不了解它工作的技术背景
以及为什么它要这样工作。这节都是关于 NGWS 虚拟对象系统的(VOS)。
以下为在VOS中形成声明、使用和管理类型模型时,NGWS runtime的规则。在VOS背后
的思想是建立一个框架,在执行代码时不能牺牲性能,允许交叉语言集成和类型安全。
我提到的框架是运行时架构的基础。为了帮助你更好地了解它,我将它勾出四个区域。
当开发C#应用程序和组件时,理解它们很重要。
VOS类型系统——提供丰富的类型系统,它打算支持全面编程语言的完全实施。
元数据——描述和引用VOS类型系统所定义的类型。元数据的永久格式与编程语言
无关,但是,元数据拿自己当作一种互换机制(nterchange mechanism)来使用,
这种互换是在在工具和NGWS的虚拟执行系统之间。
通用语言规范(CLS)——CLS定义了VOS中类型的子集,也定义了常规的用法。
如果一个类库遵守CLS的规则,它确保类库可以在其它所有能实现CLS的编程语言上使用。
虚拟执行系统(VES)——这是VOS实时的实现。VES负责装入和执行为NGWS运得时
编写的程序。
这四个部分一起组成了NGWS runtime架构。每一部分在下面小节中描述。
2.2.1 VOS类型系统
VOS类型系统提供丰富的类型系统,它打算支持多种编程语言的完全实施。所以,
VOS必须都支持面向对象的语言和过程编程语言。
现在,存在着很多种近似但有点不兼容的类型。就拿整型当例子,在VB中,它是
16位长,而在C++中,它是32位。还有更多的例子,特别是用在日期和时间以及数据库
方面的数据类型。这种不兼容使应用程序的创建和维护不必要地复杂化,尤其当程序
使用了多种编程语言时。
另一个问题是,因为编程语言之间存在着一些差别,你不能在一种语言中重用
另一种语言创建的类型。(COM用二进制标准接口部分地解决了这个问题)。 当今代码
重用肯定是有限的。
发布应用程序的最大障碍是各种编程语言的对象模型不统一。几乎每一方面都
存在着差异:事件、属性、永久保存(persistence)等等。
VOS这里将改变 这种现象 。VOS定义了描述值的类型,并规定了类型的所有值
所必须支持的一条合约。由于前面提到的支持面向对象和过程编程语言,就存在着
两种值和对象。
对于值,类型存储于表述(representation)中,同样操作也在其中实行。对象
更强大因为它显式地存于表述中。每一个对象都有一个区别于其它对象的识别号。
支持不同的VOS类型在第四章 "C#类型"中提出。
2.2.2元数据
尽管元数据用于描述和引用由VOS类型系统定义的类型,但它还不能锁定到这个单个
目标。当你写一个程序时,通过利用类型声明,你所声明的类型(假定它们是数值类型或
引用类型)被介绍给NGWS runtime类型系统。类型声明在存于PE可执行文件内部的元数据
中得到描述。
基本上,元数据用于各项任务:用于表示NGWS runtime用途的信息,如定位和装载类、
内存中这些类的事例、解决调用 、翻译IL为原始码、加强安全并设置运行时上下文边界
。
你不必关心元数据的生成。元数据是由C#的"代码转IL编译器"(code-to-IL compiler,
不是JIT编译器)生成的。代码转IL编译器发送二进制元数据信息给PE文件,是以标准的方
式发送的,不象C++编译器那样,为出口函数创建它们自己的修饰名字。
你从元数据和可执行代码并存所获得的主要优势为,有关类型的信息同类型自身固定
在一起,不会遍布很多地方。同样有助于解决存在于COM中的版本问题。进一步地,你
可以在相同的上下文中使用不同的版本库,因为库不仅被注册表引用,也被包含在可执行
代码中的元数据引用。
2.2.3通用语言规范
通用语言规范(CLS)并不是虚拟对象系统(VOS)真正的一部分,它是特殊的。CLS定义了
VOS中的一个类型子集,也定义了必须符合CLS的常规用法。
那么,对此有什么迷惑呢?如果一个类库遵守CLS规则,其它编程语言同样也遵守
CLS规则,那么其它编程语言的客户也可以使用类库。CLS是关于语言的交互可操作性
(interoperability)。因此,常规用法必须仅遵循外部可访问项目 (externally visible
items)如方法、属性和事件等等。
我所描述的优点是你可以做以下工作。用C#写一个组件,在VB中派生它,因加在
VB中的功能是如此之强大,在C#中再次从VB类派生它。只要所有的外部可访问项遵守
CLS规则,这样是可行的。
我在这本书中出示的代码不关心CLS协定。但在构建你的类库时要注意到CLS协定。
我提供了表2.1,用以给类型和外部可访问项定义协定规则。
这个清单不完整。它仅包含一些很重要的项目。我不指出出现在本书中每一种
类型的CLS协定,所以有个好主意:当你寻找CLS协定时,至少应该用浏览该表,以
了解哪种功能有效。不要担心你不熟悉这章表中的每一个含义,在这本书中你会学到
它们。
表2.1 通能语言规范中的类型和功能
bool
char
byte
short
int
long
float
do
uble
string
object(所有对象之母)
Arrays(数组)
数组的维数必须是已知的(>=1),而且最小下标数必须为0。
要素类型必须是一个CLS类型。
类型(Types)
可以被抽象或隐藏。
零或更多的接口可以被实现。不同的接口允许拥有具有相同名字和签名的方法。
一个类型可以准确地从一个类型派生。允许成员被覆盖和被隐藏。
可以有零或更多的成员,它们是字段(fields)、方法、事件或者类型。
类型可以拥有零或更多个构造函数。
一种类型的可访问性可以是公共的或者对NGWS组件来说是局部的;但是,
仅公共成员可以认为是类型接口的一部分。
所有的值型必须从系统值型继承。异常是一个枚举——它必须从系统枚举
(System Enum)继承。
类型成员
类型成员允许隐藏或者覆盖另一种类型中的其它成员。
参数和返回值的类型都必须是 CLS 协定 类型。
构造函数、方法和属性可以被重载。
一个类型可以有抽象成员,但仅当类型不被封装时。
方法
一种方法可以是静态、虚拟或者实例。
虚拟和实例方法可以是抽象的,或者是一个实现。静态方法必须总拥有一个实现。
虚拟方法可能是最后的(或者不是)。
字段(Fields)
可以是静态或者是非静态。
静态字段可以被描述或只初始化。
属性
当获取和设置方法而不是使用属性语法时,属性可以公开。
获取的返回类型和设置方法的第一个参数必须是相同的CLS类型——属性的类型。
属性名字必须不同,不同的属性类型用于区分是不充分的。
由于使用方法实现属性访问,如果 PropertyName 是同一个类中定义的一个属性,
你不能实现命名为 get_PropertyName 和 set_PropertyName 的方法。
属性可以被索引。
属性访问必须遵循这种命名格式:get_ProName,set_PropName。
枚举(Enumerations)
强调类型必须是byte、short、int 或long。
每一个成员是一个枚举类型的静态描述字段。
一个枚举不能实现任何接口。
你允许给多字段设定相同的值。
一个枚举必须继承系统枚举(隐含在C#中)
异常
可以被引发和被捕获。
自定义异常必须继承系统异常。
接口
可需要实现其它接口。
一个接口可以定义属性、事件和虚拟方法。实现取决于派生类。
事件
增加和取消方法必须是都提供或者都没有 ,每一种方法采用一个参数,
它是一个从系统代表元(System Delegate)派生下来的类。
自定义属性
可以仅使用下更类型:Type(类型),char, char, bool, byte, short, int, long,
float,do
uble, enum (一种CLS 类型), and object.
代表元(Delegates)
可以被创建和被激活
标识符(Identifiers)
一个标识符的第一个字母必须来自一限制集。
通过大小写在单一范围内,不可能唯一地区别两个或更多个标识符(大小写不敏感)。
2.2.4虚拟执行系统(VES)
虚拟执行系统实现了虚拟对象系统。通过实现一个负责NGWS runtime的执行引擎
(execution engine,缩写EE)创建VES。这个执行引擎执行你用C#编写和编译的应用程序。
下列组件为VES的一部分。
1、中间语言(IL)——被设计为很容易受各种各样的编译器所兼容 。在该框架之外,
C++、VB和C#编译器都能够生成IL。
2、装入受管代码——这包括解决内存中的名字、 表层类(laying out classes ),
并且创建JIT编译所必需的存根。通过执行经常性校验,包括加强一些访问规则,
类装载器同样也增强了安全性。
3、用JIT转换IL成原始代码——IL代码并不是设计成为一种传统的解释字节代码或
树型代码,IL转换是真正的编译。
4、装入元数据、校验类型安全和方法的完整性
5、垃圾收集(GC)和异常处理——两者都是基于堆栈格式的服务。受管代码允许
你动态地跟踪堆栈。要动态地识别各个堆栈框架,JITter或其它编译器必须提供一个代码
管理器。
6、描绘和查错服务——两者都取决于由源语言编译器所生成的信息。必须发出
两个映射:一个映射从源语言结构发到指令流中的地址,一个映射从地址发到堆栈框架
中的位置。当执行从IL到原始代码的转换时,这些映射被重新计算。
7、管理线程和上下文,还有远程管理——VES为受管代码提供这些服务。
虽然这个清单并不完整,但它足以让你理解运行时基于的由VES提供的低层架构。
肯定将会有专门讨论运行时的书,而这本书将稍为深入地挖掘各种话题。
2.3 小结
这一章,我带你逛了一回运行时的世界。我描述了当创建、编译和配置C#程序时
它是如何工作的。你学会了中间语言(IL),还有元数据是如何用于描述被编译为IL的类型。
元数据和IL都用于JITter检测和执行你的代码。你甚至可以选择用哪一种JITter来执行
应用程序。
在这一章的第二部分,涉及到了运行时为何按这种方式工作的理论。你学了
虚拟对象系统(VOS)和组成它的那部分。对于类库设计者最为感兴趣的就是通用语言
规范(CLS),它为基于VOS的语言交互操作设定规则。最后,你看到了虚拟执行系统
(VES)如何通过NGWS runtime实现VOS。