绪 论 本章简要介绍.NET技术的由来和发展,.NET Framework及其核心编程语言C#的基本信息,而后从一个简单的例子开始带领读者走进C#的编程世界。 1.1 Microsoft .NET技术 Internet技术在20世纪60年代末期才开始起步,却成为人类历史上意义最为深刻的变革之一。随着网络应用需求的飞速增长,以资源共享和协同工作为主要特征的网络分布计算已经成为新一代计算和应用的主流。随着主机计算向网络分布计算的过渡,软件系统的规模和复杂度呈几何级数增加,传统的软件开发方法面临着前所未有的挑战。 为突破时间、地域及平台的限制,提高软件系统的重用和集成能力,组件化软件开发技术应运而生。它将对象技术和组件技术有机地结合在一起,强调组件的可重用性和互操作性,力图通过组件集成来构建高效的分布式软件系统。20世纪90年代以来出现了3种典型的组件技术:OMG(对象管理组)的CORBA、Microsoft的COM/DCOM以及Sun公司的JavaBeans。它们各有优劣,都面临着不断改进和发展的要求,谁也没有一统天下的实力。 2000年6月,Microsoft正式推出了面向第三代Internet的计算计划——Microsoft .NET,其目标之一就是希望.NET能够取代COM,进而成为Windows应用和Web应用的主流开发模型。这可以说是Microsoft继使用Windows取代DOS操作系统之后又一项战略性的举措。从技术角度理解,.NET是一个全新的计算平台,它的主要特点如下: * 面向异构网络、硬件平台和操作系统,为软件提供最大限度的可扩展性、互操作性及可重用性。例如,它能够将PC上的软件方便地移植到手机、PDA等终端中。 * 实现软件系统之间的智能交互和协同工作,提高整个网络的效率和利用率,特别是实现企业级的系统集成和资源优化,给开放型企业的生产力水平带来质的飞跃。其中的一个关键组件就是以XML和SOAP协议为基础的Web服务。 * 提供一个标准化的、安全的、一致的模型和环境,简化分布式应用程序的开发难度,从而大幅度提高软件系统的质量和生产率。例如,.NET支持在不同编程语言开发的组件之间进行无缝的交互和集成。 .NET在理念中包含了对操作系统和计算机网络设计思想的延伸,即将Internet本身作为构建新一代操作系统的基础。Microsoft的宏伟目标是让.NET彻底改变软件的开发方式、发行方式和使用方式,构建第三代Internet平台,解决各种协同合作问题,实现信息的高效沟通和分享,让整个Internet为人们提供最全面的服务。 Microsoft为.NET技术制定了一套完整的规范,并将它提交到ECMA等标准化组织,以促进.NET的广泛应用。这套规范就是公共语言架构(Common Language Infrastructure,CLI)。它包括如下组成部分。 * 通用类型系统(Common Type System,CTS):定义了一套类型系统的框架,规定了数据类型的声明、使用和管理方法。.NET中的任何类型都必须遵守其中的约定。 * 公共语言规范(Common Language Specification,CLS):一组语言规则的集合。如果某种编程语言符合其中的所有规则,它就是标准的.NET编程语言,可以实现与其他.NET编程语言的跨语言集成;如果某个组件中的代码使用了其中规定的功能,它就是标准的.NET组件,可以实现与其他.NET组件的交互。 * 通用中间语言(Common Intermediate Language,CIL):一种中性语言,更准确地说是一套处理器无关的指令集合。可以针对不同的平台将通用中间语言翻译为可执行的机器指令(二进制代码),而任何.NET编程语言所编写的代码可以被编译成通用中间语言指令集。 * 其他相关的标准化文件、格式、规定等。 1.2 .NET Framework 公共语言架构是.NET的技术规范,Microsoft依据该规范在Windows平台上开发了一个完整的实现,这就是.NET Framework,它包括.NET类库和公共语言运行时(Common Language Runtime,CLR)两大部分,其整体环境结构如图1.1所示。 图1.1 .NET Framework环境结构 其中,.NET类库定义了大量的可重用对象和组件,开发人员可以利用这些现成的对象和组件快速开发各种服务和应用。不论是.NET类库中预定义的对象和组件,还是开发人员自行创建的对象和组件,它们都运行在CLR这个平台上,而底层平台的操作细节则由CLR来负责实现。 1.2.1 公共语言运行时(CLR) 从技术上说,公共语言运行时就是一个虚拟机,它为各种.NET应用提供了一个高性能的、抽象于底层操作系统和硬件的运行时环境。CLR的主要功能体现在三个方面。 1.管理代码的执行 各类.NET应用程序的代码被编译为通用中间语言。在程序执行时,CLR将通用中间语言翻译为具体的机器指令,负责加载所需的元数据类型、组件及其他各种资源,并在执行过程中提供安全性管理、错误处理、垃圾回收、版本控制等服务。 由于CLR对程序执行细节进行了封装,开发人员就可以专注于程序的业务和功能流程。此外,CLR提供了一致的实现模型,各类组件和程序能够以统一的方式进行交互。因此,CLR是在.NET Framework上实现程序高度的互操作性、集成性以及可重用性的关键所在。 为了充分利用CLR所提供的强大功能,Microsoft推荐使用托管代码(Managed Code)来开发各种.NET应用。托管代码是指满足公共语言规范要求、所使用元数据类型均属于通用类型系统、编译器面向CLR的程序代码。使用托管代码开发的对象,其整个生命周期都在CLR的管理范围当中,因而能够享受跨语言集成、跨语言异常处理、增强的安全性、版本控制和部署支持、简化的组件交互模型、调试和分析等多种服务。 然而,.NET也允许开发人员使用非托管代码来绕过CLR的某些服务,或自行实现CLR的某些功能。托管代码和非托管代码之间可以相互调用。 2.提供通用类型系统 CLR定义了一套完整的、符合通用类型系统约定的元数据类型集合,这为组件的资源控制、版本管理,以及组件之间的交互提供了关键性的信息。 CLR利用通用类型系统对代码进行严格的验证,以保证组件和程序的可靠性。开发人员所定义的各种新类型都从CLR的元数据类型中派生,这样新类型在使用的过程中就能完全遵守通用类型系统的约定。现有的及新增的.NET语言都以CLR为基础,这样使用不同编程语言的开发人员就能够方便地协同工作,共同构建应用程序。例如,在C#语言编写的程序中,可以继承使用VB .NET语言编写的类,调用它的对象和方法。 3.提供系统服务 CLR屏蔽了底层操作系统和硬件的特性,应用程序是在该虚拟机上而不是在操作系统上直接运行。在需要进行资源管理、内存分配、系统API访问等工作时,由CLR与操作系统打交道,.NET组件和应用程序则使用CLR提供的统一接口,这不仅简化了开发难度,也大大方便了程序的跨平台移植。 1.2.2 .NET类库 .NET类库是一个完全面向对象的类型集合,主要包括类、接口和值类型,其中的各种预定义类型提供了支持Windows和Web应用开发的丰富功能,具体包括下列内容。 * 基础类型定义:包括各种数学类型及其运算、字符串类型、对象类型等。 * 数据结构封装:包括集合、链表、队列、堆栈等。 * Windows界面要素:包括按钮、菜单、工具栏、文本框、列表框、数据绑定控件等。 * Web界面要素:能够实现Windows界面所具有的大部分功能,还包括Web客户端所特有的要素,如验证控件、客户端缓存等。 * Web Service:包括服务描述、消息与响应、协议等。 * XML文档处理:包括XML文件、属性、元素、节点、读写器、解析器等。 * 文件输入输出:包括驱动器、目录、文件、流、读写器等。 * 数据访问:包括数据连接、适配器、数据集、表格、记录、主键、关联等。 * 类型反射:包括程序集、类和对象、方法、属性、字段等元数据类型信息的获取。 * 异常处理:包括系统、应用程序、数学运算、安全性限制等引发的异常。 1.3 C#语言简介 20世纪80年代以来,C/C++语言一直是使用最为广泛的商业化开发语言。但在带来强大控制能力和高度灵活性的同时,其代价是相对较长的学习周期和较低的开发效率,同时对控制能力的滥用也给程序的安全性带来了潜在的威胁。C++语言过度的功能扩张也破坏了面向对象的设计理念。因此,软件行业迫切地需要一种全新的现代程序设计语言,它能够在控制能力与生产效率之间达到良好的平衡,特别是将高端应用开发与对底层平台访问紧密结合在一起,并与Web标准保持同步,C#(读作C-Sharp)语言就是这一使命的承担者。 C#语言是从C/C++语言发展而来的,它汲取了包括C++、Java、Delphi在内的多种语言的精华,是一种简单易学、类型安全和完全面向对象的高级程序设计语言。它的设计目标就是在继承C/C++强大功能的同时,兼有RAD(快速应用程序开发)语言的高效性。作为.NET的核心编程语言,C#充分享受了CLR所提供的优势,能够与其他应用程序方便地集成和交互。下面是对它的几个突出特点的描述。 * 语法简洁:C#取消了指针、也不定义烦琐的伪关键字;它使用有限的指令、修饰符和操作符,语法上几乎不存在任何冗余,整个语言结构十分清晰。初学者通常能快速掌握C#的基本特性,而C/C++程序员转入C#则几乎不会有任何障碍。 * 完全面向对象:C#具有面向对象的语言所应有的基本特性:封装、继承和多态性。它禁止多继承,禁止各种全局方法、全局变量和常量。C#以类为基础来构建所有的类型,并通过命名空间对代码进行层次化的组织和管理。许多精巧的对象设计模式都在C#语言中得到了有效的应用。 * 与Web紧密结合:借助Web服务框架,C#使得网络开发和本地开发几乎一样简单。开发人员无需了解网络的细节,可以用统一的方式来处理本地的和远程的C#对象,而C#组件能够方便地转变为Web服务,并被其他平台上的各种编程语言调用。 * 目标软件的安全性:C#符合通用类型系统的类型安全性要求,并用CLR所提供的代码访问安全特性,从而能够在程序中方便地配置安全等级和用户权限。此外,垃圾收集机制自动管理对象的生命周期,这使得开发人员无须再负担内存管理的任务,应用程序的可靠性进一步得到了提高。 * 版本管理技术:C#在语言中内置了版本控制功能,并通过接口和继承来实现应用的可扩展性。应用程序的维护和升级更加易于管理。 * 灵活性与兼容性:C#中允许使用非托管代码来与其他程序(包括COM组件、WIN32 API等)进行集成和交互。它还可以通过委托(delegate)来模拟指针的功能,通过接口来模拟多继承的实现。 Microsoft推出了新一代集成开发环境——Visual Studio .NET,支持对C#等.NET语言的可视化编程,使开发人员能够方便地创建、调试和发布程序,从而快速地构建各类.NET应用。 C# 2.0的新增特性 作为一个新生事物,.NET技术很快取得了巨大的成功。Internet上仿佛一夜之间出现了不计其数的Web服务组件,大量的网站(特别是电子商务网站)升级到了.NET平台,包括Borland在内的许多开发工具厂商纷纷推出了自己的.NET编程语言。据统计,在美国有一半以上的开发人员在使用Visual Studio .NET,而C#则成为其中最受欢迎的开发语言。然而,.NET前进的脚步并没有因此而停止。2003年初.NET Framework从1.0版本升级到了1.1版本,主要内容是对.NET类库的修改和完善,并通过.NET Framework精简版强化了对各种智能设备编程的支持。Microsoft还在Windows 2003中内置了.NET Framework 1.1。 作为Microsoft .NET战略计划的关键一步,.NET Framework 2.0版本的推出则标志着.NET技术一次全新的重大升级。.NET Framework 2.0中主要新增了以下功能。 * 对64位处理器和操作系统的全面支持。 * 对IPv6网络协议的支持,以及网络连接中的变化检测。 * 全新的数据访问策略ADO .NET 2.0。 * 全新的Web应用开发平台ASP .NET 2.0。 * 增强的证书管理功能,以及访问控制列表编程方法。 * 增强的COM互操作性。 * 高安全性的加密数据流。 * 在通用类型系统中对泛型的支持。 * 事务管理和线程管理。 与此同时,C#语言也升级到了2.0版本,以全面支持在.NET Framework 2.0上的编程。新一代Windows Vista操作系统内置了.NET Framework 2.0,Visual Studio 2005也提供了对C# 2.0可视化编程的支持。C# 2.0中的新增特性主要有以下几个方面。 * 泛型(Generic Type):这是从C++的模板发展而来的一个特性,它允许在类型定义中使用参数来指代抽象数据类型,使用时再将其替换为具体的数据类型,从而实现更高级别的代码重用。C#将类的继承机制和泛型有机地结合起来,把面向对象的方法学推向了一个新的高度。泛型的概念适用于类和结构(见第9章)、也适用于接口和单独的方法(见第11章)。 * 可空类型(Nullable Type):这是泛型的一个具体应用,它允许在变量中包含未确定的值,这就为值类型的处理带来了更大的方便(见第10章)。 * 遍历器(Iterator):允许使用foreach循环结构来遍历集合中的每个元素,这是面向对象设计模式的一个精巧应用(见第12章)。 * 匿名方法(Anonymous Method):可以把一段代码直接作为参数使用,而无需显式地定义一个方法,这样不仅减少了编程的工作量、提高了代码的可维护性,更极大地方便了程序中的各种运算方式(见第13章)。 * 分部类型(Partial Type):允许将一个类型的定义分布到多个代码段乃至多个源文件中,从而提高代码的可读性和可维护性(见第17.1节)。 1.4 开发第一个C#应用程序 先看一个最简单的C#应用程序:向用户输出一行文本。打开Windows记事本,输入如下内容,并以文件名P1_1.cs进行保存(后缀名cs表示C#源文件)。 //程序清单P1_1.cs: using System; namespace P1_1 { public class FirstProgram { public static void Main(string[] args) { Console.WriteLine("Welcome to .NET"); } } } 接下来通过Windows开始菜单中的“程序 | Microsoft .NET Framework SDK | SDK命令提示”打开SDK命令提示窗口,定位到C#源文件所在的目录,输入命令行csc P1_1.cs并按Enter键以编译程序,而后通过命令行P1_1执行程序,那么窗口中将输出一行文本Welcome to .NET,如图1.2所示。 图1.2 程序P1_1的运行结果 这里使用的程序csc.exe是Microsoft的C#编译器,其主要编译选项如表1.1所示。 表1.1 csc编译器主要选项 选 项 用 途 输出选项 /out: 指定生成文件的路径名 /target: exe 生成控制台应用程序格式 /target: winexe 生成Windows应用程序格式 /target: dll 生成动态链接库格式 /target: module 生成模块格式 /doc: 指定同时生成的XML文档的路径名 /platform: 指定运行代码的目标平台 输入选项 /reference: 指定一组要引用的程序集文件 /addmodule: 指定一组要添加的模块文件 /resource: 编译时嵌入指定资源 /linkresource: 编译时链接指定资源 /win32res 指定Win32资源文件 /win32icon 指定Win32图标文件 信息选项 /debug [+/-] 指定是否生成调试信息 /optimiz: [+/-] 指定是否进行编译优化 /incremental [+/-] 指定是否对源文件进行递增式编译 /warn: 设置警告级别,可为1~4级 /warnaserror [+/-] 指定是否把警告信息视为错误 /warnaserror [+/-]: 指定是否把列出的警告信息视为错误 /nowarn: 编译过程中不生成列出的警告信息 /checked [+/-] 指定是否将算术运算溢出视为异常 /unsafe [+/-] 指定是否允许使用不安全的代码 其他选项 /nologo 编译时不包含版权信息 /noconfig 编译时不包含配置文件信息 /help 列出编译命令选项 /codepage: 指定所使用的代码页 /main: 指定某个类的Main方法作为程序入口 例如,下面的命令表示在编译时进行代码优化,生成名为FirstProgram.exe的文件,并在程序中加入调试信息: csc /debug+ /optimize+ /out:FirstProgram.exe P1_1.cs 而如果使用Visual Studio .NET集成开发环境,那么可通过菜单命令“文件| 新建| 项目”(或快捷键Ctrl+Shift+N)打开如图1.3所示的对话框,在“项目类型”视图中选择Visual C#节点,在“模板”视图中选择“控制台应用程序”,必要时指定项目的名称和位置,而后单击“确定”按钮以创建项目。之后就可以在Visual Studio的代码编辑器中输入程序清单P1_1.cs中的内容,使用快捷键Ctrl+F5生成并执行程序。 图1.3 使用Visual Studio创建控制台应用程序 1.5 C#程序的基本结构 本节以程序P1_1为例,对C#程序的基本结构进行简要分析。 1.5.1 程序集 人们最早进行程序设计时需要从零开始编写代码,包括自己定义所需的所有类型和方法。随着程序设计技术的发展特别是高级语言的出现,开发人员开始在自己的程序中调用许多其他程序模块中的元素,从而提高开发效率。这些程序模块可能是自己或他人所设计的,也可能是系统中预定义的。 .NET则更进一步以程序集(Assembly)为单位来对应用程序进行组织。可以将一个程序集看作是一个或多个物理程序模块的组合,它是.NET程序运行和发布的最小单位。.NET 类库中的对象和组件都包含在各个程序集中,而最基本的一个程序集名为System,其中包括了大量的基础类型定义,如对象类object,以及字符、字符串、整数、浮点数等基本数据类型。程序P1_1中使用的Console类也属于该程序集。 在使用csc编译程序时,已经隐式地引用了System程序集。要显式地引用某个程序集,需要在csc编译命令中加上reference选项,如: csc /reference: mscorlib.dll P1_1.cs 该命令表示添加了对动态链接库文件mscorlib.dll的引用,而System程序集正驻留在该文件中。引用的程序集文件一般为动态链接库文件(后缀名为dll)或可执行文件(后缀名为exe),而一个物理文件中可以包含一个或多个程序集。上面的程序编译生成的可执行文件P1_1.exe也是一个程序集,程序集的名称就叫做P1_1(在namespace关键字后指定)。 下面的程序P1_2就引用了程序P1_1生成的程序集。 //程序清单P1_2.cs: using System; using P1_1; namespace P1_2 { class SecondProgram { static void Main(string[] args) { Console.WriteLine("Reference to FirstProgram: "); FirstProgram.Main(args); } } } 该程序中调用了程序集P1_1中类FirstProgram的Main方法。编译程序P1_2的命令为: csc /r:P1_1.exe P1_2.cs 这里的/r:是对编译选项/reference:的简写。如果要引用多个程序集,则可以在csc命令之后使用多个引用编译选项/r:,后接程序集所在的文件。要注意,引用的是程序集(P1_1.exe)而不是源文件(P1_1.cs)。执行该程序后,输出的结果为: Reference to FirstProgram: Welcome to .NET 而在使用Visual Studio创建程序项目时,所需的基本程序集会自动添加到项目中,并在编译程序时被自动引用。如果要添加新的程序集引用,那么可使用菜单命令“项目| 添加引用”打开如图1.4所示的对话框,在其中选择指定的程序集名称,而后单击“确定”按钮即可。如果要引用的程序项目与当前项目位于同一个解决方案中,那么在对话框中的“项目”选项卡中直接选择项目名称即可。 图1.4 在Visual Studio中添加程序集引用 1.5.2 命名空间 在代码中使用其他程序集中的元素有两种方式,一种是使用全名,另一种是使用简称。这两种方式都离不开命名空间这个概念。 命名空间是C#代码的基本组织形式,类、结构等各种类型定义都包含在命名空间中,而一个命名空间也可以嵌套在另一个命名空间之中,这就形成了一个逻辑上的层次体系结构。一个程序集中可以包含多个命名空间,但每个命名空间只能属于一个程序集。引用了命名空间之后,在程序中就可以自由地使用其中所定义的各种类型。 命名空间的使用还有利于解决命名冲突的问题。例如,两名程序员在自己的程序中都定义了一个叫做Student的类,但类的定义和所提供的功能各不相同,这样其中一人要在程序中调用另一人的程序就会产生混淆。而如果这两个程序定义了不同的命名空间(如一个命名空间叫做Graduate、另一个叫做Undergraduate,那么Graduate.Student和Undergraduate.Student就是两个不同的类),这个问题就迎刃而解了。 如果没有引入命名空间,那么在调用其他命名空间中的元素时就应当使用全名,这时除了元素名之外,还必须指明元素所属的命名空间。例如在Undergraduate命名空间中要使用Graduate中的Student类,就应该表示为Graduate.Student。 另一种方法是使用简称,即首先在代码中引入命名空间,然后在不引起混淆的情况下,就可以直接使用该命名空间中的各种类型成员。C#使用关键字using来引入命名空间,例如下面这行代码就引入了命名空间System,它包含在.NET类库的System程序集之中: using System; 有了这句声明,在代码中就可以直接使用所引用的命名空间中各种元素的名称。例如: Console.WriteLine("Welcome to .NET"); 它实际上是下面这行代码的简写: System.Console.WriteLine("Welcome to .NET"); 命名空间使用关键字namespace定义,如程序P1_1自身所定义的命名空间就叫做P1_1,该程序又引入了命名空间System,并且在Main方法中调用了Console类的WriteLine方法。而程序P1_2自身所定义的命名空间叫做P1_2,它同时引入了命名空间System和P1_1。同样,如果没有引用声明using P1_1;,那么程序P1_2中调用输出的语句应使用全名: P1_1.FirstProgram.Main(args); 1.5.3 类型和方法 在命名空间之下可以定义和使用各种类型。在C#中,类(class)是一种最基本的类型,而程序所实现的功能也主要由类及其成员来提供。看程序P1_1中FirstProgram类的定义: public class FirstProgram C#中不允许使用全局类型,如全局字段、全局方法等,只有将这些类型都定义为某个类的成员才是合法的。例如FirstProgram类的Main方法就表示应用程序的入口点(每个C#可执行程序都必须有一个执行入口): public static void Main(string[] args) 该方法定义时需要有static修饰符,且返回类型必须是void或者int,而方法的参数要么为空,要么为string[]类型。如果源文件中不包含这个方法,就会产生一个编译时错误。正是该方法中包含的代码实现了输出文本的功能: Console.WriteLine("Welcome to .NET"); 除了Main方法之外,FirstProgram类并没有提供其他的方法;但正是由于Main方法不能单独存在,因此默认使用FirstProgram类来包含该方法。而如果一个程序中有多个类都定义了Main方法,那么除非显式指定其中一个作为应用程序的主方法,否则编译时也会产生错误。指定程序中的主方法需要在编译时使用/main选项,后接主方法所在的类的名称。 方法可以有参数,如P1_1.cs和P1_2.cs中的Main方法都接受了一个名为args的string[]类型的参数。作为控制台应用程序主方法中的参数,它表示程序所能接受的多个字符串参数,程序csc.exe可以跟随的编译选项也属于这种情况。看下面的程序: //程序清单P1_3.cs: using System; namespace P1_3 { class ThirdProgram { static void Main(string[] args) { Console.WriteLine("Hello:"); Console.WriteLine(args[0]); } } } 使用不含任何选项的csc命令编译该程序。这时如果再直接使用命令行P1_3执行程序将发生错误;此时至少需要为程序指定一个参数,如P1_3 Mike,那么程序的输出将是: Hello: Mike 和程序P1_3不同,在程序P1_1和P1_2的代码中均没有用到参数,因此尽管它们在执行时可以指定参数,例如P1_1 Mike,但参数不会起任何作用。因此P1_1.cs和P1_2.cs的主方法Main中的参数args是可以省略的,如: public static void Main() 在C#中,命名空间、类及类方法中的代码都需要包含在一对大括号“{”和“}”之中,且括号必须成对使用。在第3章和第6章中将分别对方法和类进行详细的说明。 1.5.4 程序注释 良好的注释是程序设计中的重要一环。注释主要用于对代码的用途、算法等内容进行描述。被标记为注释的内容会被编译器忽略,因而不会对生成的程序产生任何影响;所以注释有时候也被用来忽略无用的代码。C#语言支持两种形式的注释:多行注释和单行注释。 多行注释也叫规则注释,开始标记为一个反斜杠加一个星号“/*”,结束标记则正好相反,为一个星号加一个反斜杠“*/”。位于此开始标记和结束标记之间的内容均被视为注释。例如: /* 第一个C#程序 本程序用于在控制台上输出一行字符 */ Console.WriteLine("Welcome to .NET"); 单行注释开始于两个反斜杠,作用范围直至该行结束。例如: //调用FirstProgram所定义的方法 FirstProgram.Main(args); 优秀的程序员都应当养成注释代码的习惯。对程序进行注释不是浪费编程时间,而是为了使程序更加清晰、完整,方便自己和他人阅读,从而提高编程效率。 在对C#程序进行注释时要注意:多行注释不要相互嵌套。这是因为编译器在分析源代码时,从遇到第一个“/*”标记开始,将忽略随后的“/*” 标记,直到遇上下一个与之匹配的标记“*/”才认为注释结束,这样编译器就会对多余的“*/”报告错误,认为没有“/*”与之相匹配。例如在下面的语句中,最后一行的“*/”会被视为不合法的代码: /* 第一个C#程序 /*本程序用于在控制台上输出一行字符*/ */ 1.6 与用户进行交互 前面介绍的程序功能都太简单,除了输出文本之外不能完成其他任何工作。绝大多数程序都免不了要与用户进行各种交互,本节将介绍如何实现这一功能。 1.6.1 控制台交互 程序P1_1的文本输出功能是通过Console类完成的。Console究竟是什么呢?它是在命名空间System预定义的一个类,用于实现控制台应用程序的输入输出。Console类中常用的输入输出方法如表1.2所示。 表1.2 Console类的常用方法 名 称 接受参数 重载 返回值类型 用 途 Read 无 无 int 从输入流读入下一个字符,至换行符结束 ReadKey 无 无 ConsoleKeyInfo 从输入流读入一个字符 ReadLine 无 无 string 从输入流读入一行文本,至换行符结束 Write string 有 void 输出一行文本 WriteLine string 有 void 输出一行文本,并在结尾处自动换行 其中Write和WriteLine两个方法有多种重载形式,既可以接受多种类型的参数(包括字符串、字符、整数、浮点数等),还可以对输入的参数进行格式化输出。本质上来说,这些重载形式都是将不同类型的参数转换为字符串格式后输出。例如: Console.Write(123); //输出整数 Console.WriteLine(123); //输出整数并换行 Console.WriteLine(15.25); //输出浮点数并换行 这些方法中的一类重载形式提供了很有用的参数格式化输出能力,即将形如{0}、{1}的指代标记包含在方法的第1个字符串参数中,而使用方法的第2个参数来取代标记{0}、第3个参数来取代标记{1},依此类推,最终完成对方法第一个参数的格式化并输出。例如: Console.Write("Hello, {0}", "Mike"); 上面这行代码在格式化之后,Write方法第1个参数中的指代标记{0}将被第2个参数的值“Mike”所替换,所以输出将是“Hello, Mike”。而下面一行代码的输出将是“Project Console, Language C#, Ver 2005”: Console.Write("Project {0}, Language {1}, Ver {2}", "Console", "C#", 2005); 下面的程序演示了如何根据用户的输入而作出不同的响应: //程序清单P1_4.cs: using System; namespace P1_4 { class SelectOutput { static void Main() { //显示提示信息 Console.WriteLine("请选择语言:"); Console.WriteLine("1. 英文 2. 简体中文 3. 繁体中文"); //读取并判断用户输入 int i = Console.Read(); if (i == '1') Console.WriteLine("C# Progamming Language Tutorial"); else if (i == '2') Console.WriteLine("C#语言程序设计基础"); else if (i == '3') Console.WriteLine("C#語言程式設計基楚"); } } } 直接使用csc命令编译并运行该程序。程序首先提示用户按下一个数字键,并根据用户的选择输出不同的内容,如图1.5所示。 图1.5 程序P1_4的运行结果 这段程序本身不难理解,但有两个值得注意的问题: ① Console的Read方法返回整数类型int,但这个返回值和用户输入的整数并不相同,需要将用户的输入转换为字符格式(即为数字加上一对单引号)之后方能进行比较。例如判断输入的整数是否为1,下面的代码对用户按下数字键并不响应: if (i == 1) Console.WriteLine("C# Progamming Language Tutorial"); ② 使用Read方法读取输入并不立即响应,而是要等到用户按下Enter键之后。如果要即时做出响应,可以改用Console的ReadKey方法,如程序P1_5所示: //程序清单P1_5.cs: using System; namespace P1_5 { class SelectOutput1 { static void Main() { //显示提示信息 Console.WriteLine("请选择要显示的内容:"); Console.WriteLine("1. 英文 2. 简体中文 3. 繁体中文"); //直接读取并判断用户输入 ConsoleKeyInfo k = Console.ReadKey(); if (k.KeyChar == '1') Console.WriteLine("C# Progamming Language Tutorial"); else if (k.KeyChar == '2') Console.WriteLine("C#语言程序设计基础"); else if (k.KeyChar == '3') Console.WriteLine("C#語言程式設計基楚"); } } } 1.6.2 Windows窗体应用程序 前面所介绍的C#程序均为控制台应用程序,程序总是在命令行窗口运行。编写控制台应用程序对于学习一门计算机高级语言来说是个不错的选择,本书中的示例程序将以控制台程序为主。不过,实际商业应用中的软件大多需要丰富的图形用户界面(Graphical User Interface,GUI)。Windows操作系统本身就是一个图形界面操作系统。每个基于.NET平台的Windows应用程序至少应包含一个Windows窗体,用户通过窗体提供的可视化操作方式与程序进行交互,如单击鼠标、选择菜单项、编辑文本等。 .NET类库中为所有的Windows窗体提供了一个基础类型Form,该类位于System.Windows.Forms程序集中。开发人员在编写自己的Windows应用程序时,可以直接使用这个类,也可以从基类Form出发创建自己的窗体,这种方式在面向对象的编程概念中叫做继承。 下面的程序用于向用户显示一个Windows窗体: //程序清单P1_6.cs: using System; using System.Windows.Forms; namespace P1_6 { static class EntryPoint { static void Main() { Application.Run(new Form()); } } } 代码非常简单,其中定义了一个EntryPoint类,只包含一个应用程序入口方法Main,用于创建并显示一个窗体类Form: Application.Run(new Form()) 方法中使用的Application是System.Windows.Forms程序集中的一个类,代表当前的Windows应用程序实例。通过调用Application类的Run方法,使得所创建的窗体成为Windows应用程序的主窗体。可以使用下面的命令编译该程序: csc /r:system.dll /r:system.windows.forms.dll P1_6.cs 这样就生成了一个可执行文件P1_6.exe,它的确是一个Windows应用程序,只不过由于csc编译器默认的生成目标是控制台应用程序,即隐含的编译选项是/t: exe,所以得到的程序并没有加上Windows应用程序的标志。这样在Windows资源管理器中用鼠标双击该可执行文件,程序将首先打开一个控制台窗口,然后才显示一个Windows窗体,如图1.6所示。关闭该窗体,程序也就执行完毕。 图1.6 程序P1_6的运行结果 要解决这个问题,需要在编译程序时指定生成Windows可执行文件。可以使用下面的命令重新编译该程序,之后在资源管理器中双击P1_6.exe即可直接打开Windows窗体: csc /t:winexe /r:system.dll /r:system.windows.forms.dll P1_6.cs 如果使用Visual Studio集成开发环境,在如图1.3所示的“新建项目”对话框中选择模板为“Windows应用程序”,那么创建的项目中将包含两个类:一是含有Main方法入口点的Program类,另一个是窗体类Form1(从Form继承)。在Visual Studio中打开窗体的设计视图,可使用鼠标从“工具箱”中拖放各种界面元素到窗体上,还可以在“属性”窗口中设置窗体和控件的外观(如通过Text属性设置控件文字,通过BackColor属性设置背景色等),如图1.7所示。 这里从“工具箱”中选取一个Button控件拖放到窗体上,双击该控件可切换到窗体的代码视图,在自动生成的窗体成员方法中输入如下代码: private void button1_Click(object sender, EventArgs e) { MessageBox.Show("欢迎使用.NET!"); } 之后使用快捷键Ctrl+F5生成并执行程序,可以看到打开的窗体中包含一个按钮控件;单击按钮将弹出一个消息框,其中显示的内容为“欢迎使用.NET!”。这里MessageBox类表示消息框,其Show方法用于显示指定的消息内容。在第6.5节中将会介绍Windows控件的事件处理方法,而第8.3节将进一步介绍窗体和控件类的用法。 图1.7 设计Windows窗体 本 章 小 结 .NET技术可以分为两大部分:规范(公共语言架构CLI)和实现(.NET Framework)。C#是专门针对.NET的高级编程语言,它在保持强大功能的同时,能够显著地提高现代软件的开发效率。C# 2.0是对C#语言的一次全面升级,使用它编写的程序具有更为优越的可重用性、可维护性和灵活性。 类是C#语言中最基本的一种数据类型,程序功能主要通过类和类的方法来实现。C#使用命名空间来组织各种数据类型,而命名空间又包含在程序集当中。这种层次化的结构是C#程序的基本组织方式。 任何可执行程序都必须有一个入口点,该入口由一个Main方法定义。不管是控制台应用程序还是Windows应用程序,都免不了要和用户进行交互。Console类提供了控制台应用程序与用户进行交互的基本方法。 习 题 1 1.选择题 (1)以下不属于.NET编程语言的是______。 A.Java B.C# C.VC .NET D.VB .NET (2)C#语言经编译后得到的是______。 A.汇编指令 B.机器指令 C.本机指令 D.Microsoft中间语言指令 (3)C#程序的执行过程是______。 A.从程序的第一个方法开始,到最后一个方法结束 B.从程序的Main方法开始,到最后一个方法结束 C.从程序的第一个方法开始,到Main方法结束 D.从程序的Main方法开始,到Main方法结束 (4)Console标准的输入和输出设备分别是______和______。 A.键盘 B.鼠标 C.屏幕 D.打印机 2.问答题 (1)请简要说明C#源程序的组成部分。 (2).NET Framework 的SDK中提供了一个反汇编工具ildasm.exe,利用它可以读取动态链接库文件或可执行文件中包含的元数据。通过“开始”菜单命令中的“Microsoft .NET Framework SDK v2.0| Tools| MSIL反汇编程序”启动该工具,试使用它查看System程序集中包含了哪些主要的命名空间和具体类型。 18 C#语言程序设计基础 17 第1章 绪论