第2章

ASP.NET编程基础

通过前一章的学习,相信您已经对ASP.NET和Visual Studio 2010开发平台有了一个初步的认识,您也许已经希望马上动手开发一个Web应用系统或者网站。不过先不要着急,虽然有这么一个强大的平台,但是没有开发语言的支持也是“巧妇难为无米之炊”。

本章首先将带领您一起安装当前最新的.NET开发平台Visual Studio 2010,并对该平台的操作进行简单的介绍。然后对该平台支持的程序开发语言C#进行讲解。在这里将对C#基础中最实用的知识点进行介绍,通过通俗易懂的例子介绍C#的语法并展示C#的功能,让您能够快速地掌握这门语言,从而进行.NET平台下ASP.NET Web应用程序的开发。C#语言目前已发展到4.0版本,它内容丰富、博大精深。对于使用C#语言进行ASP.NET开发的初学者来说,掌握C#语言是很关键的。本章将首先介绍基于C#2.0和C#3.0的基础知识,包括数据类型、变量和常量、流程控制,另外还会介绍面向对象的基础,最后介绍C#3.0/4.0的新特性。当然,对于有使用C#语言开发ASP. NET 2.0 Web应用程序经验的您可以跳过本章前几节,着重关注C#3.0/4.0的新特性。

本章我们还将通过CLR和MSIL展示程序在.NET平台内部运行的实现机制,让您对.NET平台有比较全面的理解,从中体会到这个开发平台的优越性。

昨日,伴着深秋的阳光,我们对ASP.NET有了一个初步的认识,您也亲自实现了一个Hello ASP.NET的示例程序。俗话说:“磨刀不误砍柴功”,现在就让我们将注意力放回到Visual Studio 2010开发平台的安装和使用以及C#语言的基础之上。说到Visual Studio 2010和C#语言,浮现在我脑海中的是两个词语——兵器和内功。

在武侠小说中,武林高手们各个身怀绝技,他们的神奇经历影响了一代又一代的读者。其中有的人物因为手握威力无比的绝世兵器而威震天下,例如金庸先生笔下的金毛狮王谢逊凭借一把屠龙刀行走江湖,峨嵋派掌门灭绝师太因为倚天剑而名声大震;此外,其他拥有极其高深内功修为的武林高手更是威名远扬,武林大侠郭靖和乔峰的降龙十八掌、英俊小生段誉的六脉神剑,明教教主张无忌的九阳神功,当然不能忘了少林中令人敬仰的扫地僧;更甚者便是集玄铁剑与极高内功修为于一身的神雕侠杨过。

如果把我们的程序世界比做武侠小说中的“江湖”,作为“江湖人士”的您肯定希望能够有所作为。下面首先让我们一起挥舞手中Visual Studio 2010这件强大的兵器,然后修炼一下C#语言的内功基础,为后面对网上商店系统的实现、为今后的“笑傲江湖”打下坚实的基础。

2.1 兵器篇——.NET集成开发环境Visual Studio 2010

日后,当修炼已成的您“行走在江湖之上”时,路人问道:“大侠,您使用的是什么兵器呢?”如果此时您不能准确而精辟地描述出手中是何兵器,那岂不是让人笑掉大牙,因此,还是首先来介绍一下您的兵器——Visual Studio 2010吧。

Visual Studio 2010是Microsoft公司面向Windows 7,Office 2010以及Web 2.0浪潮的新一代开发工具,代号为“Hawaii”。它是对Visual Studio 2008开发环境一次及时、全面的升级。

先来回顾一下Visual Studio 2008的特点:Visual Studio 2008在Visual Studio 2005的基础上引入了250多个新特性,整合了对象、关系数据及对XML数据的访问方式。使用Visual Studio 2008可以高效地进行各种应用程序的开发。设计器中可以实时反应变更,XML、JavaScript中智能感知功能可以提高开发效率。同时Visual Studio 2008支持项目模板、调试器和部署程序,可以高效开发Web应用,并集成了ASP.NET AJAX 框架,包含了ASP.NET AJAX项目模板。同时使用它还可以高效地进行Office应用和Mobile应用的开发。

而Visual Studio 2010在Visual Studio 2008的基础上又新增了5大特性:支持云计算架构;Agile/ Scrum开发方法;搭配Windows 7与Silverlight 4;发挥多核并行运算威力;更好地支持C++。

同时,Visual Studio 2010在以下三个方面还进行了突破创新。

1.释放创意

使用 Visual Studio 2010,可以使用原型制作、建模和可视设计工具实现您的愿景。创造和分享您的构想并发挥团队的创意力量。创建任何您能想到的功能,打造敏捷的团队,并开创更多的可能性。

通过新的原型制作、建模和可视设计工具,您可以为Windows和Web构建创新应用程序;遇见创意设计的力量——与SketchFlow,Microsoft Expression® Studio共同设计新一代使用体验,并与 Team Foundation Server完美集成;利用多核编程和云开发工具提供的新机遇和功能。

2.通过集成实现简化

Visual Studio 2010是一个集成环境,开发人员可以使用现有技能建模、编码、调试、测试和部署大量应用程序类型。Visual Studio 有助于简化常用任务并帮助开发人员深入探索平台。

单一集成开发环境进一步考虑了您的技能因素,并按照您的工作方式进行了调整;在不离开Visual Studio 2010环境的情况下,完成所有编码、建模、测试、调试和部署工作;利用现有的标准和技能适应日益增多的应用程序类型,包括 Microsoft SharePoint® 和 Windows® AzureTM;通过多屏幕支持,更多的合作伙伴解决方案和功能更强的工具编辑器满足您的多元化工作方式。

3.确保代码质量

Visual Studio 2010提供了强大的工具用于管理项目、维护源代码和发现 Bug。测试人员和开发人员可以使用手动和自动测试,以及高级调试工具,确保以正确的方式构建正确的应用程序。

具备主动项目管理功能的强大测试工具可帮助您以正确的方式构建正确的应用程序;使用新的IntelliTrace调试程序隔离应用程序历史记录中的故障点;借助主动项目管理工具(包括新报告、仪表板和计划工作表)避免问题的出现;使用手动和自动测试工具了解您是否以正确的方式构建了正确的应用程序。

小提示

Visual Studio 2010包含了4个版本:Visual Studio 2010 Professional,专业版、Visual Studio 2010 Premium,企业版、Visual Studio 2010 Ultimate,旗舰版和Visual Studio Test Professional,测试人员版等,本书中采用的是Visual Studio 2010简体中文旗舰版。

因此,建议您也使用这个版本的Visual Studio 2010,当然,请支持正版。

下面,我们就来看看Visual Studio 2010的安装过程。

2.1.1 Visual Studio 2010的安装

在安装Visual Studio 2010开发工具前最好先安装Internet信息服务(IIS,Internet Information Server)。

问题又来了,那什么是IIS呢?

IIS是一种Web(网页)服务组件,其中包括Web服务器、FTP服务器、NNTP服务器和SMTP服务器,分别用于网页浏览、文件传输、新闻服务和邮件发送等方面,它使得在网络(包括互联网和局域网)上发布信息成了一件很容易的事。IIS作为当今主流的Web服务器之一,提供了强大的Internet和Intranet服务功能。

虽然Visual Studio 2010自带了一个Web编译环境,可以把它看做是IIS的一个精简版本,可以用来对开发的程序进行调试,但是最好单独安装IIS,这样可以使用IIS提供的全部功能。

1.在Windows XP操作系统中安装Internet 信息服务(IIS)

IIS在默认情况下安装在Windows 2003 Server版本中,其他版本的操作系统可以使用“控制面板”中的“添加或删除程序”来安装IIS或其他组件。安装前在光驱中插入操作系统的安装光盘。

01 单击“开始”菜单中的“控制面板”命令,进入到“控制面板”窗口中,然后选中“添加/删除程序”选项,打开“添加或删除程序”对话框。

02 单击“添加或删除程序”对话框中左侧的“添加/删除Windows组件”选项,打开“Windows组件向导”对话框,如图2-1所示。

图2-1 “Windows组件向导”对话框

03 选中“Internet信息服务(IIS)”复选框,单击“详细信息”按钮查看安装IIS所需要的组件,如图2-2所示。

图2-2 Internet信息服务(IIS)子组件

04 选中需要安装的组件,一般将所有的选项都选中。然后单击“确定”按钮,返回“Windows组件向导”对话框,单击“下一步”按钮,即可开始安装Internet信息服务(IIS),安装界面如图2-3所示。

图2-3 IIS安装进程

05 安装完成后,单击“完成”按钮完成IIS的安装过程。

2.安装Visual Studio 2010旗舰版

01 将Visual Studio 2010安装光盘放入到计算机的光驱中,光盘运行后系统自动进入安装程序界面,如图2-4所示。该界面中共有两个选项,分别是安装Visual Studio 2010和检查Service Release。

图2-4 Visual Studio 2010安装程序界面

02 单击“安装Microsoft Visual Studio 2010”选项,安装程序弹出如图2-5所示的安装向导界面。

图2-5 Visual Studio 2010安装向导界面

03 单击安装向导中的“下一步”按钮,安装程序转入到安装程序的起始页界面,如图2-6所示。页面左侧显示的是关于安装Visual Studio 2010所需的组件信息,右侧显示的是用户许可协议。

图2-6 安装程序起始页

04 选中“我已阅读并接受许可条款”单选项,单击“下一步”按钮,进入安装程序的选项界面,如图2-7所示。可以在左侧选择安装的功能,一般情况采用默认的选择,也可以根据计算机存储空间确定产品安装的路径。

图2-7 安装选项界面

05 完成功能选择和安装路径的设置后,单击“安装”按钮,进入程序的安装界面,如图2-8所示,页面中显示当前正在安装的组件。

图2-8 安装进度界面

06 安装完成后,进入安装程序的完成页面,如图2-9所示。单击“完成”按钮完成Visual Studio 2010开发环境的安装。

图2-9 安装程序完成页面

2.1.2 常用功能窗口介绍

好,在Visual Studio 2010安装完成之后,让我们亮出自己的“兵器”,看看它有哪些用途。

单击“开始”→“所有程序”→“Visual Studio 2010”→“Visual Studio 2010”命令,运行Visual Studio 2010,开发环境界面如图2-10所示的Visual Studio 2010起始页。在这个页面中包括“最近使用的项目”、“Visual Studio指南和资源”和“解决方案资源管理器”等窗体。

“最近使用的项目”中包含的项目,为开发人员访问最近编辑过的项目提供一种快捷方式,另外在“最近使用的项目”中还提供“打开项目”和“新建项目”两个功能。“打开项目”可以选择需要打开的C#项目,“新建项目”可以帮助您创建一个新的C#项目。

“解决方案资源管理器”主要用于维护和管理当前项目中的文件。

图2-10 Visual Studio 2010起始页

单击图2-10中所示的“新建项目…”后会弹出“新建项目”对话框,在其中创建一个新的ASP.NET Web应用程序。图2-11所示为Visual Studio 2010的开发环境界面,Default.aspx为网站默认主页名。

图2-11 Visual Studio 2010开发环境

在图2-11中,最上面的是菜单栏,显示了所有可用的操作方式,具体如图2-12所示。

图2-12 Visual Studio 2010菜单栏

下面简单介绍菜单栏中重要菜单项的常用功能。

文件:主要提供新建、打开、保存、添加、关闭文件等操作。

编辑:在编辑代码时的一些操作,如复制、粘贴、查找等。

视图:用于显示或隐藏集成开发环境的各种窗口、工具栏以及其他组成部分。

项目:主要用于添加新项、添加现有项、复制网站、添加引用、添加Web引用等操作。

生成:主要用于生成网站、发布网站等操作。

调试:主要用于启动调试、开始执行等操作。

数据:主要用于模式比较、数据比较和T-SQL编辑等操作。

工具:主要用于执行连接到数据库、连接到服务器等命令。

测试:主要用于各种测试操作,如新建测试、加载元数据文件、创建新测试列表等。

分析:主要用于代码的分析,如在网站上进行代码分析、代码分析配置等操作。

窗口:主要用于工程中窗口的布局,包括新建窗口、拆分、浮动、可停靠、隐藏等操作。

帮助:提供帮助信息,方便用户学习和掌握.NET的使用方法。

图2-11中菜单栏下面的就是工具栏,里面提供了可以快速访问常用菜单命令的图标,如图2-13所示。

图2-13 Visual Studio 2010工具栏

工具栏中包括了Visual Studio 2010中大多数常用的命令按钮,如“新建项目”、“保存”、“复制”、“粘贴”、“启动调试”、“解决方案资源管理器”、“属性窗口”、“工具箱”、“起始页”、“命令窗口”等快捷按钮。

图2-11的左侧就是Visual Studio 2010的工具箱,一般情况下是隐藏状态。可以使用“视图”→“工具箱”命令打开,如图2-14所示。

图2-14 工具箱

在工具箱中,包括了所有Visual Studio 2010提供的控件。在使用时,只需将这些控件从工具箱拖放到项目相应的视图中即可。这样的方式可以节省编写代码的时间,从而加快程序开发进度。

图2-11所示的中间位置为Visual Studio 2010的主窗口,在里面可以进行网站页面的设计以及代码的编写,如图2-15所示。

图2-15 Visual Studio 2010的主窗口

可以将.NET平台提供的控件直接拖放到这里面,同时后台代码也是在这个区域进行编写的。在图2-15的下方,正如我们前面提到的,有三个不同的选项:“设计”表示当前为设计模式,即在浏览器上直接显示的效果;“源”为源码模式,在这里可以直接编辑页面的源代码;“拆分”表示两种混合模式,当前图2-15显示的即为混合模式,上部分显示的为源码,下部分显示的为设计效果。

图2-11所示的最右边为解决方案资源管理器窗口,如图2-16所示。在这里显示的是当前整个项目的文件列表,在这里面可以快速选择需要编辑的文件。

图2-16 解决方案资源管理器

在图2-16中,以aspx为后缀的文件为前台Web页面文件,以cs为后缀的为后台代码页面,主要包括对各类控件事件的编写,Web.config为配置文件。

在解决方案资源管理器下面的为属性窗口,如图2-17所示。在图中显示的是一个Button按钮控件的属性。在Visual Studio 2010中,每个控件都有自己的属性,可以在属性窗口中对属性进行设置。

图2-17 属性窗口

开发环境的最下面是调试窗口,如图2-18所示。在编写完程序进行调试的时候,如果程序有错误可以在这里进行显示,便于发现并修改错误。

图2-18 调试窗口

Visual Studio 2010是一个开发平台的集合,除了可以开发基于B/S架构的Web应用程序外,还可以开发基于C/S架构的Windows窗体应用程序、Windows Presentation Foundation应用程序,以及进行Office和水晶报表开发等。在Visual Studio 2010开发平台上可以支持C#,VB,C++等开发语言。Visual Studio 2010支持的开发类型如图2-19所示。

图2-19 Visual Studio 2010支持的开发类型

2.1.3 帮助系统

在Visual Studio 2010中最主要的帮助系统就是它提供的帮助文档。帮助文档MSDN是关于Visual Studio 2010的一个资料库,里面包含了所有的原始文档和用于与Visual Studio 2010集成的其他所有帮助。在这里面汇集了开发人员总结出来的开发经验以及比较好的解决方案,因此通过对帮助文档的了解可以及时获得所需要的技术知识。

帮助文档MSDN的使用有两种方式,最简单的一种就是在程序的开发过程中按键盘上的“F1”键,它是激活在线帮助的快捷键。另外一种方式可以通过选择“开始”→“所有程序”→“Microsoft Visual Studio 2010”→“Microsoft Visual Studio 2010文档”打开帮助系统的启动页,如图2-20所示。

图2-20 帮助系统主页面

使用MSDN帮助文档可以采用搜索方式查找所需要的内容,如图2-21所示,在搜索框中键入需要查找的内容并单击“搜索”按钮即可。

图2-21 使用MSDN帮助

关于Visual Studio 2010开发平台的介绍就到这里吧,怎么样,对您的“兵器”还满意吗?相信在今后不断地练习之后,会对它更加熟悉、更加得心应手。

喝杯咖啡,休息一下

OK,一下子说了这么多,您也看了这么久,该休息一下了,毕竟身体是革命的本钱嘛。喝杯咖啡,听听音乐,起身眺望窗外,活动活动身体,打开电视,电视里正在播放《奋斗》,对,像陆涛、华子和向南一样奋斗!向您推荐《编程之道》中的一段话:

宇宙之初有道。道产生了空间和时间。空间和时间便是编程设计之阴阳。不能领悟此道的编程者总是耗尽他们所要编写的程序的时间和空间;而领悟此道的编程者却总有足够的时间和空间来达到他们的目标。

除此之外,难道还有其他的情形吗?

2.2 内功篇—— C#语言基础

仅有一件强大的兵器,没有高深的内功修养,很显然是不能成为一个真正的“武林高手”的。当然,对于内功的修炼是一个漫长的过程,有时候也显出几分枯燥,引用孟子的一段话:“天将降大任于斯人也,必先苦其心志,劳其筋骨,饿其体肤,空乏其身,行拂乱其所为,所以动心忍性,增益其所不能……”。

2.2.1 C#简介

首先,我们还是来认识一下C#这门语言,了解一下它的成长历程。

C#是随微软的.NET技术一起成长的新一代面向对象的编程语言。C#自从2001年2月问世以来到现在已经发展到4.0版本。C#的发展历程如表2-1所示。

表2-1 C#发展历史

从发布到目前的C#4.0版本,一路走来,C#不断地改进和完善其自身的功能,深受广大程序开发人员所喜爱。由于C#面向对象的特性、强大的功能及其Visual Studio系列对C#语言的支持,在不到8年的发展时间内,C#就已经打破了Java语言一统天下的局面,发展成为目前应用面向对象进行各种应用开发最流行的工具之一。C#语法结构简单,非常容易上手,因此它也成了很多初学者在学习软件开发之初,打开潘多拉宝盒的那把钥匙。

学习和了解一门程序开发语言,首先要了解该语言的语法结构。这如同学习英语一样,需要掌握一定数量的词汇和句子结构。C#语言和英语同样是语言,只不过前者是计算机能够识别的,后者是人类进行交流的,所以掌握基本的语法是让计算机能够展示设计的应用系统的基础。当然您也不要担忧,C#不会像英语那样让人头痛,掌握C#语言的难度是远远低于英语的。

下面几节,我将带领您一同静下心来修炼一下内功心法,为您讲解C#语言的一些基础知识。当然,我也不会过多地对C#语言的技术细节进行深入的讲解,因为我们的目的是一起快速地开发一个网上商店ASP.NET应用程序。您也完全不会感觉到曾经对编程有偏见的一种说法:“编程是一种痛苦!”。通过对这些知识的学习,您也许会发出这样的惊叹:“哇,编程也不过如此!”。

在对这些基础知识的讲解过程中,让我们把将要实现的网上商店的业务情况考虑进来,通过这样的方式来学习C#。

Are you ready? Let's go!

2.2.2 C#的数据类型

C#这门年轻的程序开发语言站在Microsoft巨人的肩膀上,它集众家之长,比如Visual Basic,Java以及C++等,但又有自己的特点,比如它没有了C++中指针的概念,同时它是一门完全面向对象的语言,包含了类、对象、继承、封装、重载等概念。另外,C#中所有的类型均直接或间接地继承自System.Object基类。关于面向对象的这些概念,会在2.2.5小节中详细介绍。

C#中的数据类型主要分为两类:值类型和引用类型。

值类型又称为数值类型,包含简单类型、结构类型和枚举类型3种。

引用类型有6种,包含类类型、数组类型、接口类型、委托类型、对象类型和字符串类型。

也许您要问,值类型和引用类型到底有什么区别?这两个词字面上看起来都挺头痛的。其实两者是通过它们在计算机中存储的方式进行分类的。下面通过一个比喻在您脑海中建立一个简单而直观的印象。

可以把计算机中的内存单元看成一个个抽屉,值类型的对象是直接可以从抽屉里面拿出来的;引用类型的对象也对应一个抽屉,但是在这个抽屉里拿出来的是一个抽屉号码,通过这个号码找到另外一个抽屉,在找到的这个抽屉里面放的才是引用类型对象的值。

您看到这么多的术语也许就头痛了,有9种之多呢。其实它们是很简单的,只是说法比较特别而已,完全不需要像英语单词那样记住。下面将一一为您揭开它们看似神秘的面纱。

小提示

关于引用类型和值类型,它们背后有太多太多的故事,涉及.NET Framework的CLR(Common Language Runtime,公共语言运行时)的运行方式,涉及对象在内存中托管堆、运行时栈的空间分配等问题,是一个比较复杂的概念,它需要您对.NET Framework有一个全面的、彻底的了解。在此,我不将.NET Framework的底层概念一一讲述,您可以借助其他的参考书籍,慢慢地体会其中的博大精深,例如一本叫做《框架设计(第2版):CLR Via C#》的书,就是一本很优秀的、关于.NET内部实现的书籍。

当然,我还是建议您在有一定的C#程序开发经验和体会之后再去学习底层的.NET运行机制,那会稍微容易一点儿。

1.值类型

值类型是在它自己分配的内存中直接存储数据。值类型包括简单类型、结构类型和枚举类型。

简单类型

简单类型是直接由元素构成的数据类型,可细分为整数类型、实数类型、布尔类型和字符类型。

以商店中的商品和它的价格为例吧,请看下面的一小段程序:

int productId = 5;       //商品的编码
double price = 38.50;    //商品的价格

productId表示商品的数量,前面的int表示它为整数类型。整数类型用来表示现实世界中的整数。C#支持8种整数类型,如long,short等,它们之间的区别在于表示整数的范围。

第二行代码中的price表示商品的价格,商品的价格有可能是整数,也有可能是小数,因此用double表示,即实数类型。实数类型可以用来表示现实世界中所有的实数。根据表示范围和精度的不同,C#也支持多种实数类型,诸如float,decimal等。

整数类型和实数类型完全能够表示现实世界中所有与数字有关的问题。这样就能够显示世界中的所有问题了吗?回答当然是否定的。曾经有这样一句话:“计算机程序 = 逻辑+数字”。所以计算机程序经常需要处理一些与逻辑相关的操作。

比如用户想要在网上商店买东西,需要登录到系统,系统通过什么来识别用户是否登录了呢?实际上需要一个如下的布尔逻辑值作为判断的元素。

bool loginFlag = true; //设置登录标志

在C#中使用bool来说明loginFlag是布尔类型的,表示用户登录的状态。布尔类型有两个取值,分别为true和false。这两个值是计算机程序进行条件选择、循环执行等操作的核心,本章将会在流程控制小节中展示其强大的魅力。

在计算机程序中除了处理数字之外,最主要的信息就是字符了。字符包括数字字符、英文字符等。在C#中使用char来表示字符类型,它只能包含一个字符。因此字符可以用来表示网上商店用户的性别信息,例如:

char gender = '男'; //性别

后面我们将介绍的字符串类型(string)也可以用来表示用户的性别信息,读者可以对比一下如下的代码:

string gender = "男";  //性别

这两种方式外观上并没有多大的区别。但是在C#中,char和string有着本质上的区别,前者是值类型,后者是引用类型。

小提示

目前家用的计算机内存都可以达到4GB了,多用少用几个存储单元对程序的功能没有丝毫影响。但是目前计算机程序算法设计追求的是“时间更少、空间更小”。对初学者而言,注意节省存储空间也是一种良好的编程习惯,这也为以后开发高效率、高性能的大型应用程序奠定良好的基础,当然这是后话了。

结构类型

前面介绍了C#中的简单类型,但是现实世界中的事物仅靠这些简单类型是很难表达的。为了表达网上商店中的各种商品,包括商品名称、编码、价格以及厂家等基本信息,这些信息是一个整体。如果分别用单个变量来表示,肯定会破坏商品信息的整体性,也降低了程序的可读性。

考虑到这样的问题,在C#中还提供了结构体的概念来帮助我们定义新的数据类型,从而解决这样的问题。结构体是由多个基本的数据类型组合而成的。这有点类似表格中的一条记录。

结构类型采用struct来进行定义,内部的每一个变量称为结构的成员。下面的代码2-1中定义了一个记录商品信息的结构以及如何使用结构。

struct Product  // 定义结构体
    {
        public int productId;   //定义3个成员变量
        public string productName;
        public double price;
    }
    protected void Page_Load(object sender, EventArgs e)
    {
        Product p1;  // Product p1 = new Product(); 两种方法定义P1为结果类型
        p1.productId = 1001;       //成员赋值
        p1.productName = "舒肤佳";
        p1.price = 4.50;
        Response.Write(p1.productName + " " + p1.price);
    }

代码2-1

代码中的p1是一个结构类型的变量。在结构体声明中的public表示对结构类型成员的访问权限。对结构成员的访问通过结构变量加上访问符“.”号,后跟成员名称。通过对结构成员的赋值后运行这段程序,我们可以得到如下结果:

舒肤佳 4.50

结构体是C#从C中继承下来的一种类型,与C中的结构不同的是,它里面也可以包含方法、属性、实现接口等,几乎和面向对象的类一模一样(不能实现继承)。

您也许又会有疑问了,到底在什么场合使用结构呢?用类岂不是更接近面向对象的思想?是不是可以把结构体从C#中移除?答案当然是否定的。C#不会毫无理由地引入一种数据类型。因为结构是值类型的,因此在表示如点、矩形等轻量级对象时的成本开销比较小。另外如果成员只是一些数据的时候,结构也是不错的选择。

上面的例子中,商品Product如果只包含一些数据成员,用结构是最合适不过了。但是在网上商店中,商品还需要定义一些方法、属性等,所以就只能用类来描述了。

枚举类型

在网上商店中由于要涉及商品的买卖,所以订单处理是必不可少的操作。对订单处理主要是按照其状态进行的。订单状态一般分为:待处理、已确认、已收款、已发货、已取消5种状态,通常在程序中采用数字来表示各种不同的状态。如“0”表示“待处理”,“1”表示“已确认”等。这样的表示不能说是错误的,但是很容易让程序开发人员混淆,并且使程序的可读性降低。同时程序修改难度加大,比如某天程序员想用“0”表示“已取消”,程序修改量岂不是很大?

当然功能强大的C#是不会让您失望的,它提供了枚举类型来解决这样的问题。先看下面的代码:

enum State{ 待处理, 已确认, 已收款, 已发货, 已取消};

代码定义了State为枚举类型,里面的元素如“待处理”表示一个int类型的常数。默认情况下,第一个枚举数为0,后面每个枚举数的值依次递增1。因此在此枚举中,“待处理”为0,“已确认”为1,“已收款”为2,依此类推。

枚举也可以重写默认值的初始值设定项。如下代码:

enum State{ 待处理 = 1, 已确认, 已收款, 已发货, 已取消};

在此枚举中,强制元素序列从1而不是从0开始。使用枚举元素方式如下,它可以与基础类型(int)的值相互转换。下面的代码则展示了如何使用枚举:

State s = State.已发货;
int num = (int)s;  //num的值为:4
s = (State)3;   //s的值为:已收款

因此通过枚举可以很方便地与整数进行转换,从而使程序代码具有更高的可读性和可维护性。

2.引用类型

C#提供的另一种数据类型是引用类型,引用类型的变量不直接存储所包含的值,而是指向它所要存储的值,也可以理解为引用类型存储实际数据的引用值的地址。引用类型包括类类型、接口类型、数组类型、委托类型、对象类型和字符串类型等6种类型。

字符串类型

在网上商店的程序中,商品名称、商品生产厂家以及用户登录名等信息可以采用下面的方式进行表示:

string productName = "海飞丝";
string supplierName = "宝洁";
string userName = "zhangmx";

string就是C#提供的字符串类型,在前面结构类型一节中已经遇到过它了。它是程序中非常普遍的数据类型,在string中封装了很多的内部方法,在程序开发中,只需简单地加以利用就可以了。在图2-22中显示了在string类中封装的内部方法。程序开发中只需在string变量名后加上访问符号“.”,string类封装的方法自动会显示出来。

图2-22 string类封装的内部方法(部分)

字符串除了能保存一些信息外,还可以进行一些运算。用户如果需要购买网上商店的商品必须先登录进去,那么系统是如何验证登录的用户是否合法的呢?这里就用到了字符串提供的“==”运算符。通过这个运算符来判断用户当前输入的用户名和密码是否与系统保存的用户名、密码相同,判断结果返回为bool类型:

string passWord0 = "000000";
string passWord1 = "123456";
bool result = (passWord0 == passWord1);   // 不相等,返回 false

string还提供了一个内部方法Equals()来进行字符串比较,如下所示:

bool result = passWord0.Equals(passWord1); // 采用内部方法进行比较,result值为false

小提示

对初学者而言,一定要分清“==”和“=”符号的区别,在C#语言中,“==”是比较运算符,用来判断两个变量是否相等;“=”是赋值符,用于对变量赋值。“=”在您熟悉的自然语言里是等于符号。

数组类型

数组是一组类型相同的有序数据,数组按照数组名、数据元素类型和维数来进行描述。在C#中,数组的工作方式与其他大多数开发语言中的工作方式类似,C#中的数组可以是一维数组、多维数组以及交错数组(数组的数组)。

声明一个整型数组,如下所示:

int[] a = new int[10];  //声明一个大小为10的int类型数组

数组声明的时候可以初始化数组元素的值。C#通过将初始值放在大括号{}内为初始化数组提供了简单而直接的方法。如果声明时未初始化数组,则数组成员自动初始化为该数组类型的默认初始值。

下面的示例展示了数组初始化的方法:

int[] num = new int[5] { 3, 4, 5, 6, 7 };
string[] words = new string[4] { "Welcome", "to", "online", "shop" }; //字符串数组
int[] number = new int[] { 1, 2, 3, 4, 5, 6, 7 }; //省略数组大小
string [] words = { "欢", "迎", "来", "到", "网", "上", "商", "店" }; //省略new语句

访问数组成员可以直接进行,类似在其他语言中访问数组成员的方法。如下代码创建了一个名为number的数组,然后修改第3个元素的值。

int[] number = { 3, 4, 5, 6, 7 };
number[2] = 1;

在数组中,元素的索引是从0开始的,也就是说,对于数组number,其元素个数为5,元素的索引编号是0 ~ 4。

上面主要介绍的是一维数组,下面简单介绍一下多维数组的声明,如下所示:

int[,] a2 = new int[,] { { 1, 2, 3 }, { 4, 5, 6 } }; //二维
int[, ,] a3 = new int[10, 20, 30]; //三维
int[][] r1 = new int[3][]; //可变数组,有三个元素,每个元素都是一个数组
r1[0] = new int[] { 2, 3, 4 }; //每个元素可以不等长
r2[1] = new int[] { 1, 2, 3, 4 };
r3[2] = new int[] { 1, 2, 3, 4, 5 };
int[][][][] r2; //多维可变数组
类类型

在前面介绍结构的时候已经提到了类,在网上商店中的商品、订单都需要定义属性和方法,如果用结构来描述的话就会显得捉襟见肘,因此需要用类来进行描述。代码2-2用类Product描述网上商店中的商品,该类包含数据成员和方法成员。

class Product    //声明一个类
    {           //类体
        public int ProductID;   //数据成员  商品编码
        public string Name;     // 商品名称
        public string Description;  // 商品描述
        public double Price;    // 商品价格
        public string getName()  //方法成员
        {
            return Name;
        }
        public void setName(string name)
        {
            this.Name = name;
        }
    }

代码2-2

类支持继承机制,通过继承派生类可以扩展基类的数据成员和方法成员,从而达到代码重用的目的。但是和C++中的类不同,C#中不允许类进行多层继承,只允许单继承。在后面章节将对类进行详细介绍。

接口类型

在网上商店的商品和订单中,都会对价格进行操作,在商品中需要设置或者读取商品的价格;在订单中需要获得订单中所有商品价格的总和。在程序开发中,一种可行的办法是在商品和订单类中用不同的成员方法来实现价格的操作,此外,C#还提供接口来实现。通过在接口中预先定义一系列的方法,然后在实现该接口的类中分别对这些方法进行具体实现。代码2-3展示了如何使用类来实现接口。

interface PriceInterface   //声明一个接口
    {
        double getPrice();   // 价格方法
    }
    class Product : PriceInterface  // Product类,实现PriceInterface接口
    {
        public int ProductID;
        public double Price;
        public double getPrice()   // 实现接口的getPrice()方法
        {
            return Price;
        }
    }
    class Order : PriceInterface // Order类,实现PriceInterface接口
    {
        public int OrderID;
        public double TotalPrice;
        public double getPrice()  // 实现接口的getPrice()方法
        {
            // 方法体
        }
    }

代码2-3

在上面的代码中声明了一个接口PriceInterface,它有一个成员方法getPrice。同时代码定义了商品类Product和订单类Order,并分别实现了接口PriceInterface的方法getPrice。订单中的getPrice方法体可以根据订单的业务逻辑进行设计。在这里需要了解的是,如何进行接口声明和类如何实现接口。

小提示

接口也支持继承机制,并且它可以从多个基接口中继承,同时类也可以实现多个接口,但接口中不能包含数据成员。

委托类型

在网上商店中,商品的价格不可能一直保持不变,和现实中的商品销售一样也需要进行一些促销方式,比如商品打折等。在程序的设计中,当出现打折的业务需求时,如果在出现价格的地方都进行打折换算,不仅程序修改量比较大并且也容易出错。如果商家取消打折按原价销售呢,那么所有的修改都需要还原。因此在C#中提供了委托来解决这样的问题,编程人员可以根据商家的销售策略动态地调用不同的价格计算方法。先看代码2-4,看看如何实现这个神奇的功能。

public delegate double PriceDelegate();  // 声明委托PriceDelegate
    class Product
    {
        public double Price;
        public void setPrice(double price)
    {
        this.Price = price;
    }
    public double getPrice()    // 获得商品原价的成员方法
    {
        return Price;
    }
    public double DiscountPrice() // 获得商品折扣价格的成员方法
    {
        return Price * 0.9;
    }
}
protected void Page_Load(object sender, EventArgs e)
    {
        Product product = new Product();
        product.setPrice(10.0);   // 设置商品价格
        // 通过Product的getPrice创建委托对象
        PriceDelegate price = new PriceDelegate(product.getPrice);
        Response.Write("商品原价为:" + price());
        // 通过Product的DiscountPrice 改变委托对象
        price = new PriceDelegate(product.DiscountPrice);
        Response.Write("<br>商品折扣价为:" + price());
    }

代码2-4

输出结果为:

商品原价为:10
商品折扣价为:9

代码2-4的第一行声明了委托PriceDelegate,它的返回类型为double,并且不带任何参数。在商品类Product中有两个价格成员方法getPrice和DiscountPrice,它们分别用来处理原价和折扣价格。在Page_Load方法中,通过这两个成员方法创建了不同的委托对象,在具体的业务逻辑中可以根据不同情况,采用相同的方式进行创建。通过委托在程序中根据业务逻辑的处理需求动态地调用了不同的价格处理方法,使得业务逻辑处理更加方便。

小提示

在委托使用中,任何方法都可以通过委托动态地调用,只是方法的参数类型和返回类型必须与委托的参数类型和返回类型一致。

对象类型

前面已经提到,C#是一门面向对象的语言,因此可以把所有的一切都看成对象。因此C#提供了对象类型(object),可以将其他类型转化为对象类型,对象类型是所有其他类型的基类。因此object类型可以存储任何类型的值,可以定义object类型的参数,object也可以作为方法返回值的类型。下面的代码是在object中存储商品类Product的一个实例对象。

Product product = new Product();
object obj = product;

3.类型转换——装箱和拆箱

和现实中的商店一样,在网上商店中,同时会有很多顾客在商店中选购自己满意的商品,也会出现人头攒动的热闹景象。当然我们是看不到这个热闹场面的,计算机程序才能感觉到这番景象。既然有这么多的顾客,怎么来识别他们,看不到顾客的面孔,会不会把两个或者多个顾客当成同一个人呢?有没有解决的办法呢,回答当然是肯定的,不然我们的网上商店就无法营业了。

在C#中,提供了会话(Session)来区分不同的客户。当不同的客户登录到网上商店时,服务器就会为当前的客户设置Session对象来保存不同客户的信息。即每个客户都有自己与众不同的Session,当客户购买商品的时候,通过Session,商品准确地与客户对应。

下面的代码表示客户登录到商店时,商店进行的Session设置:

Session["userId"] = user.userID;   // 用户ID
Session["usrName"] = user.userName; // 用户名称

在代码中,将客户的ID userID(int类型)和用户名userName(string类型)装入Session对象中,因为客户的ID和用户名就能够唯一标识客户。

在购买商品的时候,通过下面的代码取出会话中的信息识别客户。

int userId = (int)Session["userId"];
string userName =(string) Session["usrName"];

这样就获得了客户的ID和名称,在Session 前面的(int)表示将会话值强制转换为int类型。代码2-5展示了会话设置和读取的全过程:

class User   // 用户类
    {
        public int userID;
        public string password;
        public string userName;
    }
    protected void Page_Load(object sender, EventArgs e)
    {
        User user = new User();
        user.userID = 1;
        user.userName = "zhangmx";
        Session["userId"] = user.userID;   // 设置会话
        Session["usrName"] = user.userName;
        //  其他操作
        int userId = (int)Session["userId"];  // 读取会话
        string userName =(string) Session["usrName"];
        Response.Write("用户信息:"+userId +" "+ userName);
    }

代码2-5

在代码中,创建一个用户类User对象实例,然后对用户ID和用户名称进行赋值,并将这两个成员变量装到会话对象中。最后从会话对象中读出用户ID和用户名称,并显示出来,程序运行结果为:

用户信息:1 zhangmx

实际上,对会话进行的操作就是C#提供的装箱和拆箱机制,它是基本类型与object类型之间的相互转换。在网上商店中,Session是object类型的,装箱就是设置会话,拆箱就是读取会话。

装箱和拆箱使基本类型能够被视为对象。装箱将基本类型隐式或者显式地转换成一个object类型,如下代码所示:

int i = 123;
object o = i;  // 隐式装箱
object o1 = (object)i; // 显式装箱

拆箱过程分为两步:首先检查这个对象的实例,是否为给定的值类型的装箱值;然后将这个实例的值复制给基本类型的变量,例如:

o = 123;
i = (int)o; // 拆箱

装箱操作可以隐式进行,但是拆箱必须是显式的。

喝杯咖啡,休息一下

怎么样?您对刚刚的内容掌握了多少呢?对这些基础知识的学习是一个比较乏味的过程,但正所谓“万丈高楼平地起”,没有一个好的基础怎么行呢,您说是吧,再坚持一下,胜利就在眼前。

在进入下面的学习之前,还是让我们放松一下吧,Music!

2.2.3 变量和常量

OK,休息一下是否感觉到整个人都轻松了下来,那就让我们继续“修炼”吧,奋勇前进!

1.变量

在商品类Product的定义中,有这样的代码:

int ProductID;
string Name;
string Description;
double Price;

它们都称为变量,是用来存储商品信息的基本单元。

在C#中要使用变量,必须先定义变量的名称与数据类型,如下所示:

int i; //定义一个整型变量
string str; //定义一个字符串型变量

如上面的代码中,使用int,float,double,string和char等关键字来定义变量名称并指定其数据类型。变量在命名时需要遵循一些规则:

● 变量名的第一位不能是数字,中间不能有空格。

● 变量名中不能使用一些特殊字符,如*,&,%,^之类的字符。

● 变量名不能与C#中的关键字同名,如int,class,double等。

变量的命名也有一些风格,但是最主要还是以清晰易懂为主。尽量避免使用一些简单的字母作为变量名称,这会增加日后程序维护的困难,发生同名的几率也比较大。比较流行的变量命名通常以小写字母作为开始,并在每个单词开始时第一个字母使用大写。例如:

int numberOfProduct;

这样的名称可以让人一眼就看出变量的作用,在程序开发中这样的命名是经常采用的。

小提示

C#语言不允许使用未初始化的变量。如果变量在定义时没有进行初始化,那么在使用该变量前一定要赋值,否则会导致编译时报错。

2.静态变量

在商品类Product中,还可以以这样的方式进行变量定义,如下所示:

    class Product  // 商品类
{
        public static string company = "P&G";  //静态变量
        public double Price;   // 实例变量
        //其余成员变量
        //方法
}

与前面的变量定义相比,您会发现在变量company的前面有static修饰,这就是C#提供的静态变量修饰符号,通过这样声明的变量为静态变量。

一般在C#中,静态变量多用于类中的成员变量,表示类的所有实例对象都共同拥有的一个属性值。如上面的类Product里面的company变量值为P&G,那么由该类生成的所有实例对象的company都是P&G。

由于静态变量值不依赖类的特定对象而存在,访问的时候直接通过类名加“.”加变量名来实现。如下所示:

string name = Product.company;   //访问类中的静态变量

尽管类可以创建很多个对象,但静态变量只有一个副本。所以无论有多少个Product的对象在程序中被创建,在内存中company变量仅有一份。静态变量最好在声明时将其赋值。

3.实例变量

当然在类中,大部分的成员变量是没有static修饰的,这样的变量也称为实例变量,它们的值为类的各个特定的实例对象单独所有。比如上面代码中商品类的价格Price,各个具体的商品对象有不同的价格,因此Price必须为实例变量。

因为实例变量值是由特定对象存在的,因此访问的时候,需要通过类的实例名加“.”加变量来进行访问。

Product product = new Product();
double price = product.Price;   // 访问类中的实例变量

4.常数

根据自然辩证统一的思想,任何事物都有两方面,C#编程语言中包含有变量,那么肯定也会包含常量。

常量,顾名思义肯定是固定不变的,又称为常数,就是在程序运行过程中其值固定不变的量。常量的类型可以是任意的值类型或引用类型。

常量被声明为字段,通过关键字const进行声明,在声明的时候必须初始化。

class Calendar
    {
        public const int months = 12;   //声明months为常量,值为12
        public const int year = 2008, month = 7, day = 11;
            //同时声明多个相同类型的常量
    }

在上面的Calendar类中,定义了int类型的月份months为常量,它的值为12,然后定义了年year、月month、日day常量分别为2008,7和11。

虽然没有使用static关键字来声明常量,但可以像访问静态字段一样访问常量。通过类名加“.”加常量名来实现,如下所示:

int m = Calendar.months;    //访问常量

2.2.4 流程控制

在网上商店中,用户如果购买东西的话必须先登录,然后才能进行一系列的操作。用户在登录进行身份验证的时候有两种可能,要么是合法用户允许进入,要么是非法用户拒绝进入。程序的执行流程都是从上往下逐条顺序进行的,像用户登录这样需要选择执行代码的逻辑应该怎样完成呢?和大多数编程语言一样,C#提供了流程控制机制来实现这些逻辑功能。

流程控制依据某个表达式的结果(true或false)来决定程序要执行哪些代码,不执行哪些代码,或者决定哪些代码需要重复执行。流程控制语句主要包括条件语句和循环语句。

1.条件语句

if语句让我们能够根据某个表达式的结果来决定是否执行一个或者多个连续语句,可有可无的else子句可以连续检验多个测试条件,switch语句单独用来同时测试多个条件。

if…else语句

if…else语句是最常用的条件语句,它根据布尔表达式的值来判断是否执行后面的内嵌语句。

if…else语句的语法格式如下所示:

if( 条件)  //布尔表达式
{
    语句 //条件成立时执行,布尔表达式结果为true
}
else
{
    语句 //条件不成立时执行,布尔表达式结果为false
}

if…else这两个关键字一般是成对出现的,if关键字后面紧跟着一个条件语句,若符合这个条件语句,程序直接执行if下面的语句,否则程序流程跳到else关键字下的语句。代码2-6展示了if…else的执行情况:

protected void Page_Load(object sender, EventArgs e)
    {
        double price = 5.5;
        if ( price < 10.0)  //条件判断
        {     //条件成立 true
            Response.Write("当前价格小于10.0!");
        }
        else   //条件不成立 false
        {
            Response.Write("当前价格大于或者等于10.0!");
        }
    }

代码2-6

程序运行结果为:

当前价格小于10.0!

前面提到的用户登录网上商店的逻辑完全可以通过if…else来实现,代码2-7展示了用户登录的逻辑处理操作:

string inputId = TextBox1.Text.ToString();  // 用户输入ID
    string inputPwd = TextBox2.Text.ToString(); // 用户输入密码
    if ( inputName == user.UserId && inputPwd == user.Password )
    {
          // 用户合法,进入网上商店页面
    }
    else
    {
          // ID和密码错误,返回首页
    }

代码2-7

在代码2-7中,“&&”为逻辑表达式,在这里表示用户的ID和密码同时正确(true)才表示用户合法,它表示所有条件都满足条件表达式结果才为true。另外还有一种“||”逻辑表达式,表示所有条件至少一个条件满足就为true。

如果在if…else语句中用来判断的条件有多个,可以使用else if语句,即多重if语句。在多重if语句中,只有其中一个符合条件的语句能够被执行。

多重if语句的语法格式如下:

if( 条件1)  //布尔表达式
{
    语句  //条件1成立时执行
}
else if (条件2)  // 条件1不成立时判断条件2
{
    语句     // 条件2成立时执行
}
//其他else if 语句块
else   //所有条件都不成立时执行
{
    语句
}

在多重if语句中,只要有一个条件成立,程序流程将在执行相应代码后直接跳出多重 if语句结构。代码2-8展示了多重if语句用法,让您对多重if语句的逻辑判断流程有一个直观的了解。

protected void Page_Load(object sender, EventArgs e)
    {
        double price = 10.0;
        if ( price < 10.0 )  //条件 i < 10 不成立
        {
            Response.Write("当前价格小于10!");
        }
        else if ( price > 10.0 )     // 条件 i < 10不成立,判断 i > 10
        {
            Response.Write("当前价格大于10!");
        }
        else             //默认条件 i == 10
        {
            Response.Write("当前价格等于10!");
        }
    }

代码2-8

程序运行结果为:

当前价格等于10.0!
switch语句

如果测试的条件是对数字或者字符串进行比较匹配的操作,还可以使用switch语句来替代一大堆的if…else子句。switch语句也叫分支选择语句,它可以根据测试表达式返回的结果来决定该执行哪一个case字段的语句。

switch语句的语法格式如下:

switch( 条件)  //测试表达式
{
case value1:
              //语句块
case value2:
              //语句块
//其他case
default:  //默认的处理
              //语句块
    }

switch语句可以包含任意数目的case子句,每个case后面都指定一个常量表达式。在switch语句中,程序自上而下用测试表达式逐个对case后面的值进行比较,遇到相符的case值便执行后面的语句块,直至遇到break关键字程序就退出switch语句。代码2-9展示了switch语句的执行过程。

protected void Page_Load(object sender, EventArgs e)
    {
        Product product = new Product();
        product.CategoryID = 2;
        switch (product.CategoryID)
        {
            case 1:
                Response.Write("商品属于服装类!");
                break;
            case 2:       //满足条件
                Response.Write("商品属于日用品类!");
                break;     //跳出switch语句
            case 3:
                Response.Write("商品属于数码类!");
                break;
            default:
                Response.Write("该商品类别暂时不存在!");
                break;
        }
    }

代码2-9

在上面的代码中,创建了一个商品对象product,然后通过switch语句将product的类别编号CategoryID的值与case后面的常量依次比较,由于CategoryID值为2,与第二个case语句符合,因此程序的运行结果为:

商品属于日用品类!

小提示

在switch语句中,测试表达式的返回值必须与case后面的值类型一致,否则编译会出错。switch中测试表达式的数据类型必须是下面类型中的一种:

int,char,string,long,byte,short或enum枚举类型

在每一个case块以及default块中,必须以break结束,这也是C#与C++中switch的不同之处,switch最多只能有一个default块。

2.循环语句

流程控制的另外一种就是循环语句,它根据某个表达式的结果(false或true),重复执行单一或连续的多个语句。C#中所提供的循环语句有4种:for,while,do…while和foreach语句。

for语句

for语句是在程序中最常见的循环语句。当一个程序代码块必须重复执行,并且执行次数固定的时候就可以通过for语句来实现。for循环对于迭代数组和顺序处理非常方便,比如计算数组中100个元素之和等。

for语句的语法格式如下所示:

for( 循环变量初始值;布尔表达式;循环计数变量增加或减少)
{
        //循环体
}

代码2-10是使用for循环语句,计算从1到10的和:

protected void Page_Load(object sender, EventArgs e)
    {
        int sum = 0;
        for (int i = 1; i <= 10; i++)
        {
            sum = sum + i;
        }
        Response.Write("1到10相加的结果为: "+sum);
    }

代码2-10

代码中,变量i初值为1,首先与10进行比较,i的值小于10则执行与变量sum进行相加的操作,执行完成后变量i自增1;再与10进行比较,如果仍然小于或等于10则继续执行相加操作,直到i大于10为止;否则退出循环。程序运行结果为:

1到10相加的结果为: 55
while语句

while语句也用来进行循环操作,实现的是当型循环,先测试循环条件再执行循环。

while语句的语法格式如下所示:

while (条件)
{
    //循环体
}

当条件的返回值为true时会重复执行循环体中的代码。条件的返回结果必须是布尔值,当布尔值为false时,程序就跳出循环体。

代码2-11展示了while语句的用法,通过while语句来计算从1到10的相加值,注意比较与for语句的不同。

protected void Page_Load(object sender, EventArgs e)
    {
        int sum = 0, i = 1;
        while (i <= 10)
        {
            sum = sum + 1;
            i++;
        }
        Response.Write("1到10相加的结果为: "+sum);
    }

代码2-11

在程序中,首先判断当前的变量i的值与10的大小,若小于或等于10则与变量sum进行相加,然后变量i自增1,继续判断是否小于或等于10,若满足则继续执行,否则退出循环。因此程序输出结果为:

1到10相加的结果为: 55
do…while语句

与while语句不同的是,do…while语句实现的是直到循环,即先执行循环体再测试循环条件。do…while语句重复执行一个语句或者语句块,直到指定的条件返回值为false为止。

do…while语句的语法格式如下所示:

do
    {
        //循环体
    } while (条件);

do…while语句与while语句比较类似,不同之处在于,在每一次程序块执行完后才进行条件值的判断,若条件返回结果为true则进入下一次循环,否则跳出循环。

代码2-12展示了do…while语句的用法,同样是计算从1到10的相加的和。

protected void Page_Load(object sender, EventArgs e)
    {
        int sum = 0, i = 1;
        do
        {
            sum = sum + i;
            i++;
        } while (i <= 10);
        Response.Write("1到10相加的结果为:"+sum);
    }

代码2-12

在程序中,首先变量i与变量sum进行相加,然后i再与10进行比较,若满足条件则继续执行,否则退出循环。注意它与while语句代码的区别。程序的运行结果为:

1到10相加的结果为: 55

小提示

在大部分情况下,for,while和do…while三种循环语句都能实现同样的功能,可以互相替代。也许您不禁要问,既然一个循环语句就能解决问题,为什么还要弄三个语句呢?这样不是增加了语言的复杂性吗?虽然三种循环语句在功能上面有交叉,但是它们还是有细微差别的,也是各司其职的。

● for语句主要用于指定次数的循环。

● while语句主要用于不指定次数最少为0次的循环。

● do…while语句主要用于不指定次数最少为1次的循环。

这样的设计是为了增加语言的灵活性和方便性。在设计一种语言的时候,有时为了灵活性,可能会设计为同一个问题可以用很多种方法来实现,虽然结果一样,但实现的过程是不一样的,这涉及到时间复杂度和空间复杂度的问题,简单地说就是运行效率的问题,所以for,while和do…while在代码效率上是有差别的,它们被编译后是不一样的,执行所需要的时间也是不一样的。

3.foreach语句

foreach语句是C#语言中独创的一种循环结构。foreach语句用循环的方式访问集合以获得所需的信息,但是最好不要用来更改集合的内容以避免产生不可预知的副作用。

foreach语句的语法格式如下所示:

foreach (数据类型 变量名 in 数组或对象集合)
    {
        //循环体
    }

代码2-13展示了foreach语句的用法,拼接出网上商店的欢迎辞。

protected void Page_Load(object sender, EventArgs e)
{
string [] wordArray = new string [] { "Hello","friend",",","Welcome","to","online","shop","!"};
        string str = null;
        foreach (string word in wordArray)   //foreach 循环
        {
            str = str + " "+ word;       //拼接字符串
        }
        Response.Write(str);
    }

代码2-13

在程序中,通过foreach将字符串数组wordArray里面的元素取出来,并进行拼接。程序运行结果为:

Hello friend , Welcome to online shop !

4.跳转语句

前面我们提到程序是顺序执行的,但是在一些情况下也需要终止程序的执行,按照我们预期的流程进行执行。因此跳转语句提供了终止当前的程序流程顺序的功能,从而转到指定的程序块执行。C#中的跳转语句有4种:break,continue,goto和return。下面将对它们进行介绍。

break语句

在前面的switch语句中已经对break语句进行了一些介绍,它主要是用来终止它所在的最近的封闭循环或switch语句。前面的switch语句介绍中已经阐述了break语句如何终止switch的过程。下面主要介绍break如何在循环语句中起作用。

下面的代码将展示break跳转语句的用法:

protected void Page_Load(object sender, EventArgs e)
    {
        for (int i = 1; i <= 10; i++)
        {
            if (i == 5)
                break;   // break语句跳出当前循环
            Response.Write(i + " ");
        }
    }

在程序代码中,使用for循环输出1到10的整数。当变量i的值增加至5的时候,if条件判断为true,执行break,程序流程跳出当前for循环,程序运行结果为:

1 2 3 4
continue跳转语句

continue跳转语句将终止执行循环体内位于它后面的代码,并使它所在的循环进行新的一次循环。

代码2-14展示了continue语句的用法,跳出当前循环的一次迭代。

protected void Page_Load(object sender, EventArgs e)
    {
        for (int i = 1; i <= 10; i++)
        {
            if (i == 5)
                continue;   // continue语句跳出当前循环
            Response.Write(i + " ");
        }
    }

代码2-14

和前一个代码类似,应用for循环输出1到10的整数。当变量i的值增加至5的时候,if条件判断为true,执行continue,程序流程跳出当前的一次迭代,继续执行后面的迭代。因此会输出了从1到10中除5以外的所有整数,注意与前面代码的区别。程序执行结果为:

1 2 3 4 6 7 8 9 10

在应用中可以利用break语句在数组或者对象集合中寻找特定的值或对象,从而减少遍历整个数组或集合的运行开销。使用continue语句可以处理特定数据集合中的噪声数据,通过预设噪声数据范围,在程序运行中可以忽略噪声数据,从而增加数据的有效性和安全性。

小提示

break跳出当前的循环,循环终止;continue跳出当前循环的一次迭代,循环并没有终止。

goto跳转语句

C#还提供了一种比较古老的跳转语句——goto跳转语句,它相当于汇编语言里面的jmp指令,可以使程序跳转到开发人员指定的位置,它可以是有条件跳转也可以是无条件跳转。

代码2-15展示了goto语句的用法。

protected void Page_Load(object sender, EventArgs e)
    {
        for (int i = 1; i <= 10; i++)
        {
            if (i == 5)
                goto jmp;   // 有条件跳转到指定位置,jmp为位置标签
            Response.Write(i + " ");
        }
        jmp: goto jmpNext;    //无条件跳转
        Response.Write("this is goto statement!"); //此代码将被跳过,不会执行
        jmpNext: Response.Write("the goto is finished!");
    }

代码2-15

程序的运行结果为:

1 2 3 4 the goto is finished!

由于goto语句极易打破程序的执行顺序,破坏了程序的可读性,同时也给代码维护带来极大的不便,所以现在程序开发中很少使用该跳转语句,但您可以作为常识进行了解。

return跳转语句

在前面的商品类Product的成员方法getPrice中我们已经认识了return跳转语句,它是最常见的跳转,一般方法的返回就是通过return语句来实现的。return一般位于方法的结尾,它后面的任何语句都不会执行。如果方法无返回类型,可以利用return立即从方法中退出,如下所示:

public double getPrice()   // 获得商品价格
    {
        return Price;   // return 跳转
    }

5.异常处理

在实际的程序开发中,不仅要考虑程序的正常操作,还应该把握在现实世界中可能发生的各类不可预期的事件。比如用户的错误输入、数据库访问出错、数据类型转换出错等问题。在程序中引入异常处理机制来解决这类问题,防止系统崩溃。

C#异常处理的基本格式如下所示:

try
    {
        //程序代码块
    }
    catch (Exception ex)
    {
        //异常处理代码块
    }
    finally
    {
        //无论异常是否发生,都要执行的代码块
    }

在异常处理格式中,try用于检查并发送程序执行中发生的异常,只要try块内发生任何异常,都将被送到catch块中进行异常处理。无论是否有异常发生,finally语句块中的代码都将被执行。还有一个throw关键字,它是用来显式地抛出异常,包括预定义异常和自定义异常。

下面通过一个简单的例子简要描述异常处理的使用方法,请读者阅读代码2-16。

protected void Page_Load(object sender, EventArgs e)
    {
        String str = null;
        try
        {
            if (str == null)
            {
                throw new NullReferenceException();  //抛出异常
            }
        }
        catch (Exception ex)      //接受并处理异常
        {
            Response.Write("异常处理情况:" + ex.ToString());
        }
        finally
        {
            Response.Write("<br><br>" + "执行finally语句块!");
        }
    }

代码2-16

在代码中,根据if条件判断字符串变量str为空,通过throw显式地抛出异常类NullReferenceException的一个实例对象,在catch语句块中对异常进行处理,这里显示出发生异常的信息,最后执行finally语句块。运行结果如下:

异常处理情况: System.NullReferenceException:
未将对象引用设置到对象的实例。
在 Default2.Page_Load(Object sender, EventArgs e)
位置 c:\Documents and Settings\zhangmx\My Documents\Visual Studio
2008\WebSites\WebSite1\Default2.aspx.cs:
行号 87-执行finally语句块!

在我们的网上商店中,也需要处理很多异常,如对数据库进行访问的时候,因为访问过程中会发生很多意想不到的事情,比如网络中断、远程数据库不可连接等意外。在程序中可以在连接数据库时采用下面的方式,如下所示:

try     // 异常
    {
        //访问配置文件中的连接字符串
          String sqlConnect = System.Configuration.ConfigurationManager. AppSettings["conStr"];
        SqlConnection conn = new SqlConnection(sqlConnect); //创建连接对象
        conn.Open();     //打开连接
        ……    //数据库相关操作
        conn.Close();  //关闭数据库连接
    }
    catch (Exception ex)
    {
        Response.Write(ex.ToString());
    }

对数据库的访问将在下一章进行详细的介绍。

2.2.5 C#面向对象编程

面向对象的英文名称为Object-Oriented,简称为OO,这也是在一些软件工程书中出现频率比较高的词汇。对象是什么呢?简单来说,对象=属性+方法。属性也可以称为特性,它从静态上描述对象的各种特征;方法也可以称为行为,它从动态上描述对象能够做什么事情。

对象可以描述自然界中的任何事物,小到生物机体细胞,大到宇宙星系。对象的属性是一个对象区别其他对象的标志。比如,对象人的属性可以包括名字、学历、国籍等;而商品对象属性就包括名称、价格、生产厂商等。通过属性将人和商品区分。

对象除了用于标志自身特性的属性外,还要有活动能够做事情,即对象的方法,吃饭、思考、睡觉这些是人的行为,价格调整、买入、卖出这些是商品的行为。

C#是一门面向对象的语言,因此所有的东西都可以转化为对象。在想象对象的世界中,程序就不再是一条条执行的命令,而是很多的对象组合在一起,通过传递消息相互协作,从而实现特定的功能。比如在商店进行商品的买卖,可以转化为人和商品之间的关系。

1.类

在网上商店中,商品除了编码、名称、价格等属性,还可以进行买卖等操作,因此它们都属于一个类。

类是面向对象程序设计中一个非常重要的概念。我们知道,对象包含了属性和方法。类就是有共同属性和共同行为的对象的总称。在面向对象的C#程序设计中,所有的个体都是对象,每个对象都属于特定的类,类定义了属于该类的对象的属性和方法。比如,属于商品类的对象,都具有编码、名称、价格等属性,只是属性值不同。除属性外,它们都具有调整价格等商品类定义的行为。

在前面的小节中,已经提到用关键字class来定义类,对于商品类,用C#代码描述如代码2-17所示。

class Product    //商品类
    {
        public int ProductID;    // 编码
        public string Name;     // 名称
        public string Description;  // 商品描述
        public double Price;     // 价格
        public void setName(string name)   // 设置名称
        {
            this.Name = name;
        }
        public void setPrice(double price)  // 设置价格
        {
            this.Price = price;
        }
    }

代码2-17

商品类的雏形已经出现了,那么应该如何使用这个类呢,下面我们让它跑起来,通过代码2-18向您展示一件商品:

class Product    //商品类
    {
        public int ProductID;    // 编码
        public string Name;     // 名称
        public string Description;  // 商品描述
        public double Price;     // 价格
        public void setName(string name)   // 设置名称
        {
            this.Name = name;
        }
        public void setPrice(double price)  // 设置价格
        {
            this.Price = price;
        }
        public string showProduct()   // 显示商品信息
        {
            string proInfo = "商品名:" + Name + "  价格:" + Price;
            return proInfo;
        }
    }
    protected void Page_Load(object sender, EventArgs e)
    {
        Product product = new Product(); // 注意 C#区分大小写,Product 与 product是不同的
        product.setName("舒服佳");
        product.setPrice(4.50);
        Response.Write(product.showProduct());
    }

代码2-18

在代码中,通过关键字new创建了一个名称为product的商品类Product对象,然后通过方法分别设置商品名称和价格,名称为“舒服佳”,价格为“4.50”,最后通过调用方法showProduct,将商品展示出来。输出结果为:

商品名:舒服佳 价格:4.5
构造方法

在上面的代码中,每次创建一个商品类的实例对象时,都要通过一系列的方法进行对象属性的初始化,如设置商品价格、名称等。这样显然也增加了代码的编写量。有没有一种方法能够在创建的同时就进行对象初始化呢?答案当然是肯定的,构造方法就为我们解决了这样的问题。首先重新回顾我们的商品类,注意它与前面的区别,如代码2-19所示。

class Product    //商品类
    {
        private int ProductID;      // 编码
        public string Name;         // 名称
        public string Description;  // 商品描述
        public double Price;        // 价格
        public Product()    // 构造方法,不带参数
        {
            this.Name = "舒服佳";
            this.Price = "4.50";
        }
        public Product(string name, double price)  // 构造方法,带参数
        {
            this.Name = name;
            this.Price = price;
        }
        public string showProduct()
        {
            string proInfo = "商品名:" + Name + "  价格:" + Price;
            return proInfo;
        }
    }

代码2-19

在这个商品类中,您会发现出现了两个命名和类名Product相同的方法,不错,它们就是类的构造方法。

构造方法可以是带参数的,也可以是不带参数的,都是为了完成在创建类的实例对象时进行初始化。在创建对象的时候只有指定了参数才会执行带参数的构造方法,否则将执行不带参数的构造方法。因此不带参数的构造方法也称为默认构造方法。代码2-20展示如何使用构造方法来进行方法初始化:

protected void Page_Load(object sender, EventArgs e)
    {
      Product product = new Product();  // 创建商品product,执行默认构造方法
        Response.Write(product.showProduct());
        Product product1 = new Product("海飞丝",17.5); //创建商品product1,执行带参数的构造方法
        Response.Write("<br>"+product1.showProduct());
    }

代码2-20

在上面的代码中,通过执行默认构造方法创建了商品product,它的名称和价格设置为默认的“舒服佳”和“4.5”;商品product1是通过执行带参数的构造方法进行创建的,它的名称和价格是传入的参数“海飞丝”和“17.5”。程序输出结果为:

商品名:舒服佳 价格:4.5
商品名:海飞丝 价格:17.5

商品类的构造方法中没有进行显式设置的属性将初始化为默认值,比如商品的ProductID将默认为0。其实在类中如果不定义构造方法,系统会自动提供一个默认的不带参数的构造方法进行对象默认的初始化。

小提示

在定义构造方法的时候注意以下几点:构造方法通常与类名相同;构造方法不声明返回类型;构造方法一定是public类型的;构造方法中不要做对类的实例进行初始化以外的工作。

访问修饰符

细心的您会发现,在上面的商品类Produc中,属性和方法前面有private或者public修饰。这就是C#提供的访问修饰符,保护类的成员在不同的级别上被访问,使得类可以将自己的属性和方法设置为私有(private)或者公有(public)。

私有(private),顾名思义就是类的属性和成员只能在类中被访问,如同用户登录网上商店的ID和密码,只能自己知道,除非通过一个公有(public)方法将其暴露出来,否则别人是无法看到的。代码2-21展示在用户类中使用访问修饰符进行访问权限设置:

class User
    {
        private int userID;
        private string password;
        public string userName;
        public User(int id, string pwd, string name)
        {
            this.userID = id;
            this.password = pwd;
            this.userName = name;
        }
}
protected void Page_Load(object sender, EventArgs e)
    {
        User user = new User(1,"000000","zhangmx");
        string user1Pwd = user.userID + user.password; // 访问用户user的ID和密码,失败
        string userName = user1.userName;  //访问用户user的名字,成功
}

代码2-21

在代码中,定义了用户类User,它的ID(userID)和密码(password)的属性设置为private,因此在Page_Load方法中无法访问,因为Page_Load方法不属于用户类;由于用户名(userName)设置为public,Page_Load可以自由访问该属性。

如果要使用用户的ID和密码怎么办呢?其实很简单,只需在用户类中添加带public的方法将其读出,当然这个方法必须是用户类同意的,如下所示:

public string getUserInfo()    // 获得用户ID和密码
    {
        return userID + password;
    }

在Page_Load方法中采用user1.getUserInfo直接把用户user1的ID和密码读出。这样即使读出了用户的ID和密码私有的属性值,但是这两个属性本身并没有暴露出来,因此这样最大程度地保护了类的安全。

访问修饰符很好地支持了类很重要的一个特性——封装性。封装性指类设置某些类成员的访问性,以使外界的对象不能访问某些成员,以此来增加类的安全性。如果在类的声明中未指定访问修饰符号,则使用默认的可访问性。默认的访问修饰符为private。

小提示

C#还提供了如下几种访问修饰符,如表2-2所示。

表2-2 C#中的访问修饰符

属性

在商品类中,我们使用一系列方法对类中的私有数据成员值进行读写,如代码2-22所示。

private string Name;
public void setName(string name)   // 设置名称
    {
        this.Name = name;
    }
    public string getName()   // 获取名称
    {
        return Name;
    }

代码2-22

这样当然也是一种可行的方法,但是如果一个类中有很多私有数据成员,相应的就需要编写很多的方法来进行读取操作,这样就增加了代码量并且也不直观。有没有一种更清爽的方法来替代呢?答案当然也是肯定的,C#不会让我们失望的,它提供“访问器”来满足我们的需求。在这里我们改变一下称呼,属性就是称为“访问器”的方法。属性更充分地体现了类的封装性:不直接操作数据内容,而是通过“访问器”进行访问。

下面先见识一下“访问器”:

set{语句块}
  get{语句块}

C#提供了两种访问器,set和get。set访问器用于对数据成员的写操作;get访问器用于对数据成员的读操作。现在我们就利用这两种访问器对上面设置商品名称的读取方法进行改进,如代码2-23所示。

private string Name;
public string productName   // 定义属性
    {
        set     // 写
        {
            Name = value;
        }
        get    // 读
        {
            return Name;
        }
    }

代码2-23

这样看起来是不是很清爽,简单几行就解决了对私有数据成员的读写,现在也不由得感叹C#给带来了如此轻松和愉悦的编程体验。下面就来看看,在程序里面如何使用“访问器”读写类的数据成员,如代码2-24所示。

protected void Page_Load(object sender, EventArgs e)
    {
        Product product = new Product();
        product.productName = "舒蕾";   // 写数据
        Response.Write(product.productName);   // 读数据
    }

代码2-24

在代码中,如同使用类的公有数据成员一样使用类的属性。虽然没有直接操作数据成员,但是这一切在“访问器”内部已经完成了。

小提示

在属性定义中,get和set访问器都必须是在属性体的内部声明。

方法

在前面已经提到,类除了包括属性外,还应该具有方法,用来表示类的实例对象的行为。比如在用户类User中,方法getUserInfo用来获得用户对象的ID和密码,如代码2-25所示。

public string getUserInfo()    // 获得用户ID和密码的方法
    {
        return userID + "---" +password;
    }

代码2-25

在方法定义中,首先需要确定方法的访问权限,这里使用public表示该方法在任何地方都可以被使用;其次方法的输出是什么形式,这里string表示方法输出字符串类型,即用户ID和密码;然后需要给方法进行命名,getUserInfo;后面括号里面放的是方法的输入,即参数,在这里方法参数为空,不需要输入。

这一切完成之后,就可以编写代码来决定方法可以做什么事情了,在这里我们只需要方法getUserInfo给出用户ID和密码。到这里,方法getUserInfo就诞生了,可以被程序的其他地方使用来完成任务了。代码2-26所示为通过方法getUserInfo得到用户的ID和密码:

protected void Page_Load(object sender, EventArgs e)
    {
        User user1 = new User(1,"000000","zhangmx");
        Response.Write(user1.getUserInfo());
    }

代码2-26

如果运行程序,将得到这样的结果,前面的1是用户ID,后面的6个0为密码:

1---000000

现在问题来了,得到的是用户ID和密码的组合,怎么才能把它们分开呢?当然可以通过对返回的字符串进行操作将它们分开,或者修改getUserInfo方法体,分别返回用户ID和密码。问题又来了,一个方法只能有一个返回值,怎么能同时返回用户ID和密码呢?难道把getUserInfo拆分为两个方法,分别返回用户ID和密码?这的确是不错的解决方案,但是有些时候方法又必须要返回多个值,怎么办?

不用慌,C#又给我们带来了一份惊喜。可以这样修改getUserInfo方法,如代码2-27所示。

public void getUserInfo(out int id, out string pwd)
    {
        id = userID;
        pwd = password;
    }

代码2-27

上面代码中的out对有编程经验的您来说也许比较陌生,这是C#提供的一种全新的机制来使方法能输出多个值,即输出参数。下面看看这份惊喜是如何地让人振奋,如代码2-28所示。

protected void Page_Load(object sender, EventArgs e)
    {
        User user1 = new User(1, "000000", "zhangmx");
        int id;
        string pwd;
        user1.getUserInfo(out id,out pwd);  // 使用输出参数
        Response.Write("用户ID:" + id);
        Response.Write("<br>用户密码:" + pwd);
    }

代码2-28

在代码中首先定义需要从方法中获得的id和pwd,然后将这两个变量作为输出参数调用方法getUserInfo,之后id和pwd中就保存了用户ID和密码,这一切是如此清晰和直接。运行程序输出的结果为:

用户ID: 1
用户密码: 000000

2.面向对象的三大原则——封装、继承和多态

上一小节通过商品的例子对类的结构进行了简单的介绍,您能够快速地将现实中的一个概念转化为一个类模型。当然类只是面向对象编程的基本元素,用面向对象的技术开发应用程序还需要采用该技术提供的三大原则:封装、继承和多态。

● 封装是隐藏类的具体细节,这些细节信息是不需要被调用者所了解的,这样最大限度地保证了类的安全性。

● 继承可以简化类的设计,并提高代码的可重用性。程序员可以直接使用别人事先写好的基类中已经存在的功能,而不需要重新对功能进行编码。

● 多态可以使类中名称相同的功能具有不同的实现方式,在调用类的一些功能方法的时候不需要考虑该方法的具体实现。

封装

封装是把数据和处理这些数据的代码都集成在类中,然后向外界提供相应的属性和方法供调用者使用。同时隐藏具体的实现细节,调用者只需知道类中提供对其有用的方法。封装也可以在类的实现细节被修改时,不影响调用者与类的交互方式。比如在网上商店中,商品的编码、名称、价格等属性以及显示商品和计算价格等操作都封装在类Product中。如果某种商品需要打折,只需在类中对商品计算价格的方法进行修改,而在业务逻辑的处理中仍然可以调用原来商品计算价格的方法。可以通过代码2-29所示的方式封装一个商品类。

class Product    //商品类
{
    private int ProductID;    // 编码
    public string Name;     // 名称
    public string Description;  // 商品描述
    private double Price;    // 价格
    public Product(string name, double price)  // 构造方法,带参数
    {
        this.Name = name;
        this.Price = price;
    }
    public double DiscountProduct()
    {
        return Price;
    }
}

代码2-29

在这个商品类Product中,商品的各种属性(编码、名称以及价格等)都封装在里面,并且提供了计算商品信息的方法。调用者在使用该过程中,完全不需要考虑这个方法是如何实现的(在这里该方法很简单,直接就是返回当前商品的价格),直接调用方法就可完成这个操作。代码2-30可以实现对计算商品价格方法的调用:

protected void Page_Load(object sender, EventArgs e)
    {
        Product product = new Product("舒肤佳",4.5);
        double productPrice = product.DiscountProduct(); //调用计算商品价格的方法
        Response.Write("商品价格为:"+productPrice);
    }

代码2-30

在程序中,通过商品类的构造方法实例化一种商品,然后通过调用Product提供的DiscountProduct()方法计算该商品的价格。程序运行结果如下:

商品价格为: 4.5

如果某一天商家准备对商品进行打折促销,需要修改商品价格的计算方法,由于商品类Product提供了良好的封装,因此只需对类中DiscountProduct()的实现进行修改,在外面仍然可以通过DiscountProduct()计算商品的价格。具体代码如代码2-31所示。

class Product //商品类
    {
        private int ProductID;     // 编码
        public string Name;        // 名称
        public string Description; // 商品描述
        private double Price;      // 价格
        public Product(string name, double price) // 构造方法,带参数
        {
            this.Name = name;
            this.Price = price;
        }
        public double DiscountProduct()
        {
            return Price * 0.85; // 打折,修改方法具体的实现
        }
    }
    protected void Page_Load(object sender, EventArgs e)
    {
        Product product = new Product("舒肤佳",4.5);
          double productPrice = product.DiscountProduct(); //仍然调用DiscountProduct()方法
        Response.Write("商品价格为:"+productPrice);
    }

代码2-31

在代码中,由于商品需要打8.5折,所以在商品类Product中对DiscountProduct()进行修改。由于封装性,这个改变对调用者来说是隐藏的,因此在主程序中仍然调用DiscountProduct()方法计算商品的价格。程序输出结果如下:

商品价格为:3.825
继承

在面向对象中,类包括父类(基类)和子类(派生类)两种。被继承的类称为父类或基类;继承自别的类称为子类或派生类。继承就是类可以从父类中继承除了构造方法外的所有数据定义和方法。通过继承,一个类可以隐式地将父类中所有成员当做自己的成员。因此,继承可以提高代码的重用,减少代码编写量。

在网上商店中,日用品和饮料都属于商品。因此可以把所有的商品抽象为一个基类Procuct,其他各种商品(日用品和饮料等)可以是继承该Procuct的子类。子类和基类之间是一种“属于”的关系。

在C#中,可以通过下面的格式定义继承,饮料类Drink继承商品类Product。

class Drink : Product

在网上商店中,所有的商品都具有商品编码、价格、名称等属性,因此无论是日用品或者饮料等都必须拥有这些属性,具体实现如代码2-32所示。

class Product    //商品类
    {
        private int ProductID;    // 编码
        public string Name;     // 名称
        public string Description;  // 商品描述
        public double Price;    // 价格
        public string ShowInfo()
        {
            string strInfo = "商品名称:" + Name + "价格:" + Price;
            return strInfo;
        }
    }
    class Drink : Product      //饮料类Drink继承商品类Product
    {
        public string capacity;   // Drink类特有的容量属性
        public Drink(string name, double price)
        {
            this.Name = name;       // 继承基类的Name属性
            this.Price = price;      // 继承基类的Price属性
            this.capacity = "600ml";
        }
    }
    protected void Page_Load(object sender, EventArgs e)
    {
        Drink drink = new Drink("农夫山泉",1.2);
        string procuct = drink.ShowInfo();    // 继承基类的ShowInfo方法
        Response.Write(procuct + " 规格:"+ drink.capacity);
    }

代码2-32

在上面的代码中,饮料类Drink继承商品类Product,因此在Drink类中就继承了Product类的Name,Price等属性,同时也继承了Product类中显示商品信息的ShowInfo方法。通过继承的方式,子类Drink就如同使用自己定义的属性一样使用从父类Product继承下来的属性和方法。由于饮料需要有一个表示其容量的属性,所以在Drink类中增加了capacity属性来表示。

在主程序中,通过Drink自己的构造方法创建一个饮料商品,然后调用父类的ShowInfo方法显示该商品的具体信息。程序运行结果为:

商品名称:农夫山泉 价格: 1.2 规格:600ml

小提示

在C#中,类继承只允许单一继承,即一个类只有一个基类。如果需要使用多重继承,可以通过接口来实现。继承是可以传递的。如果A继承B,而B继承C,那么A就会既继承B中的成员,又继承C中的成员。构造方法和析构方法是不能被继承的。基类中声明为private的成员不能被子类所继承。

多态

多态可以使基类的方法在不同的子类中有不同的实现方式,比较常见的多态是通过继承来实现的。当多个类继承自同一个基类时,每个子类可以根据需要重写基类的成员来满足不同的需求。代码2-33展示了通过继承实现多态。

class Product    //商品类
    {
        private int ProductID;    // 编码
        public string Name;     // 名称
        public string Description;  // 商品描述
        public double Price;    // 价格
        public string ShowInfo()
        {
            string strInfo = "商品名称:" + Name + ": 价格" + Price;
            return strInfo;
        }
    }
    class Drink : Product      //饮料类Drink继承自商品类Product
    {
        public string capacity;   // Drink类特有的容量属性
        public Drink(string name, double price)
        {
            this.Name = name;       // 继承基类的Name属性
            this.Price = price;      // 继承基类的Price属性
            this.capacity = "600ml";
        }
        public string ShowInfo()    // 重写基类Product方法ShowInfo
        {
    string strInfo = "商品名称:" + Name + " 价格" + Price+" 规格:"+capacity;
          return strInfo;
        }
    }
    class Food : Product
    {
        string deadLine;    // Drink类特有的保质期deadLine属性
        public string capacity;
        public Food(string name, double price)
        {
            this.Name = name;       // 继承基类的Name属性
            this.Price = price;      // 继承基类的Price属性
            this.deadLine = "2009/1/1";
        }
        public string ShowInfo()     // 重写基类Product的方法ShowInfo
        {
            string strInfo = "商品名称:" + Name + " 价格" + Price+" 保质期:"+deadLine;
            return strInfo;
        }
    }
    protected void Page_Load(object sender, EventArgs e)
    {
        Drink drink = new Drink("农夫山泉",1.2);
        Food food = new Food("蒙牛酸酸乳",2.5);
        Response.Write(drink.ShowInfo() +"<br>");
        Response.Write(food.ShowInfo());
    }

代码2-33

在代码中,饮料类Drink和食品类Food分别继承商品类,在子类中通过重写ShowInfo方法满足不同类别的商品显示各自具有特性的信息。在主程序中,通过子类的构造方法创建了两个不同的商品实例,并将商品信息显示出来。代码运行结果为:

商品名称:农夫山泉 价格1.2 规格:600ml
商品名称:蒙牛酸酸乳 价格2.5 保质期:2009/1/1

3.泛型

“一次编码,多次使用”,这是C#在2.0中引入泛型的原因,它也是C#2.0的最强大的功能,在C++中,这样的机制称为模板。通过泛型可以定义类型安全的数据结构,而无须使用实际的数据类型。显然这样能够提高性能并得到更高质量的代码,因为这样可以重新利用数据处理的算法,而不需要重写类型特定的代码。下面通过一个简单的例子来展示泛型的实现过程,主要是让您了解一下泛型的定义以及实例化方法,具体实现如代码2-34所示。

public class Finder<T>   //定义一个泛型类,该类有一个类型参数 T
    {
        public static int Find(T[] items, T item)
        {
            for (int i = 0; i < items.Length; i++)
            {
                if (items[i].ToString() == item.ToString()) return i;
            }
            return -1;   // 没有找到返回 -1
        }
  }
  protected void Page_Load(object sender, EventArgs e)
  {
      // 使用 int 来实例化 Finder<T>类
      int i = Finder<int>.Find(new int[] { 1, 2, 3, 4, 5 }, 6);
      // 使用 string 来实例化 Finder<T>类
        int j = Finder<string>.Find(new string[] { "Welcone", "to","online", "shop" }, "to");
      Response.Write("the location is" + i );
      Response.Write("<br> the location is" +j );
  }

代码2-34

在代码中,首先定义了一个泛型类Finder,它有一个静态方法Find,用来查找数组里面的元素,参数的类型为T,表示可以为任意类型,将会在程序编译时被特定的类型替代。在Page_Load方法中,分别通过整型数组和字符串类型进行实例化,程序运行结果为:

the location is -1
the location is 1

C#也提供了很多的泛型集合,可以方便程序的代码编写,如使用List<T>可以用来存放商品类和用户类的实例,如对象,并对它们进行搜索、排序等操作,在网上商店中我们会有这样的代码:

static public List<Product> Products = new List<Product>{ // 商品对象}
static public List<Order> Orders = new List<Order>{ //订单对象 }

Product和Order分别表示商品类和订单类,因此泛型集合List<T>就可以对不同类型进行操作。

2.2.6 C# 4.0新特性

本书目标是致力于向您介绍最佳的C#解决方案,前面介绍的C#知识已经能够很好地解决在网上商店中遇到的问题了,这也许可以让我们满足了。但是技术的发展是没有终点的,人们总是在努力追求不断改进现有的技术。在C#3.0已经成熟并获得大家认可的时候,微软又推出了C#4.0,带来了一系列很炫很实用的特性。我们也感觉到微软的.NET开发团队在对新技术的开发上不断追求超越和创新,给我们带来了更方便更快捷的程序开发途径。下面我将带您步入C#4.0时代。

回顾C#发展的历史,C#1.0完全是模仿Java,并保留了C/C++的一些特性如struct,新学者很容易上手;C#2.0加入了泛型,也与Java 1.5的泛型如出一辙;C#3.0加入了一堆语法,并在没有修改CLR的情况下引入了LINQ,简直是神来之笔,虽然很多项目出于各种各样如性能之类的原因没有采用,但非常适合小型程序的快速开发,减轻了程序员的工作量,也提高了代码的可读性;C#4.0增加了动态语言的特性,从里面可以看到很多JavaScript,Python这些动态语言的影子。虽然越来越偏离静态语言的道路,但从另一个角度来说,这些特性也都是为了提高程序员的生产力。至于被接受与否,还是让时间来说话吧。

C#4.0语言的新特性和改进包括:

● Dynamically Typed Object——动态类型对象

● Named and optional parameters——命名和可选参数

● Improved COM Interoperability——改进的COM互操作性

● Co- and Contra-Variance——协变和逆变

● Compiler as a Service——编译器即服务

下面就一一为您介绍C#4.0的新特性。

Dynamically Typed Object——动态类型对象

C#4.0中加入了dynamic关键字,可以声明一个变量的static类型为dynamic(有点绕口)。

在C#3.0及之前,如果你不知道一个变量的类型,而要去调用它的一个方法,一般会用到反射:

object calc = GetCalculator();
Type calcType = calc.GetType();
object res = calcType.InvokeMember("Add",
BindingFlags.InvokeMethod, null,
new object[] { 10, 20 });
int sum = Convert.ToInt32(res);

有了dynamic,就可以把上面代码简化为:

dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);

使用dynamic的好处在于,可以不去关心对象是来源于COM,IronPython,HTML DOM或者反射,只要知道有什么方法可以调用就可以了,剩下的工作可以留给runtime。下面是调用IronPython类的例子:

ScriptRuntime py = Python.CreateRuntime();
dynamic helloworld = py.UseFile("helloworld.py");
Console.WriteLine("helloworld.py loaded!");

dynamic也可以用在变量的传递中,runtime会自动选择一个最匹配的overload方法。

这里有一个Demo:把一段JavaScript代码复制到C#文件中,将var改成dynamic,function改成void,再改一下构造函数的调用方式(new type()改为win.New.type()),去掉JavaScript中的win前缀(因为这已经是C#的方法了),就可以直接运行了。

dynamic的实现是基于IDynamicObject接口和DynamicObject抽象类的。而动态方法、属性的调用都被转为了GetMember,Invoke等方法的调用,如代码2-35所示。

public abstract class DynamicObject : IDynamicObject
{
public virtual object GetMember(GetMemberBinder info);
public virtual object SetMember(SetMemberBinder info, object value);
public virtual object DeleteMember(DeleteMemberBinder info);
public virtual object UnaryOperation(UnaryOperationBinder info);
public virtual object BinaryOperation(BinaryOperationBinder info,(object arg);
public virtual object Convert(ConvertBinder info);
public virtual object Invoke(InvokeBinder info, object[] args);
public virtual object InvokeMember(InvokeMemberBinder info, object[] args);
public virtual object CreateInstance(CreateInstanceBinder info, object[] args);
(public virtual object GetIndex(GetIndexBinder info, object[] indices);
public virtual object SetIndex(SetIndexBinder info, object[] indices, object value);
public virtual object DeleteIndex(DeleteIndexBinder info, object[] indices);
public MetaObject IDynamicObject.GetMetaObject();
}

代码2-35

Named and optional parameters——命名和可选参数

带有可选参数方法的声明:

public StreamReader OpenTextFile(
string path,
Encoding encoding = null,
bool detectEncoding = true,
int bufferSize = 1024);

命名参数必须在最后使用:

OpenTextFile("foo.txt", Encoding.UTF8, bufferSize: 4096);

顺序不限:

OpenTextFile(bufferSize: 4096, path: "foo.txt", detectEncoding: false);

Improved COM Interoperability——改进的COM互操作性

在C#中调用COM对象如Office对象时,经常需要写一堆不必要的参数:

object fileName = "Test.docx";
object missing = System.Reflection.Missing.Value;
doc.SaveAs(ref fileName,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing);

在C#4.0中就可以直接写成:

doc.SaveAs("Test.docx");

C#4.0对COM交互做了下面几方面的改进:

1.Automatic object → dynamic mapping

2.Optional and named parameters

3.Indexed properties

4.Optional “ref” modifier

5.Interop type embedding (“No PIA”)

对第1点和第5点的简单解释如下:

在COM调用中,很多输入输出类型都是object,这样就必须知道返回对象的确切类型,强制转换后才可以调用相应的方法。在C#4.0中有了dynamic的支持,就可以在导入这些COM接口时将变量定义为dynamic而不是object,省掉了强制类型转换。

PIA(Primary Interop Assemblies)是根据COM API生成的.NET Assembly,一般体积比较大。在C#4.0中运行时不需要PIA的存在,编译器会判断你的程序具体使用了哪一部分COM API,只把这部分用PIA包装,直接加入到你自己程序的Assembly里面。

Co-and Contra-Variance——协变和逆变

在C#中,下面的类型转换是非法的:

IList<string> strings = new List<string>();
IList<object> objects = strings;

因为你有可能会这样做,而编译器的静态检查无法查出错误:

objects[0] = 5;
string s = strings[0];

C#4.0中,在声明generic的Interface及Delegate时可以加in及out关键字,如:

public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IEnumerator
{
bool MoveNext();
T Current { get; }
}
public interface IComparer<in T>
{
public int Compare(T left, T right);
}

out关键字的意思是IEnumerable<T>中T只会被用在输出中,值不会被改变。这样将IEnumerable<string>转为IEnumerable<object>类型就是安全的。

in的意思正好相反,IComparer<T>中的T只会被用在输入中,这样就可以将IComparer<object>安全地转为IComparer<string>类型。

前者被称为Co-Variance,后者就是Contra-Variance。

.Net 4.0中使用out/in声明的接口:

System.Collections.Generic.IEnumerable<out T>
System.Collections.Generic.IEnumerator<out T>
System.Linq.IQueryable<out T>
System.Collections.Generic.IComparer<in T>
System.Collections.Generic.IEqualityComparer<in T>
System.IComparable<in T>

托管:

System.Func<in T, …, out R>
System.Action<in T, …>
System.Predicate<in T>
System.Comparison<in T>
System.EventHandler<in T>
Compiler as a Service——编译器即服务

C# 4.0中增加了与编译器相关的API,这样就可以将字符串作为代码动态编译执行。

2.3 .NET框架

通过前面的学习,我们已经知道C#语言的编译器专门用于.NET框架里,即用C#编写的代码总是在.NET框架中运行。在这里也需要弄明白C#和.NET的关系:C#就其本身而言只是一种编程语言,尽管用于生成面向.NET环境的代码,但是它本身不是.NET的一部分。因为C#语言是和.NET一起使用的,如果要使用C#高效地开发应用程序,理解.NET就非常重要了,因此接下来我们将介绍.NET的内涵。

.NET框架是架构在Windows平台上的一个虚拟的运行平台,并且在理论上,C#也可以是一种跨平台的语言。如果将Windows平台换成Linux,同样可以使用符合CLS(通用语言规范)的.NET语言来创建应用程序。因此包括C#语言在内的所有.NET语言都可以编译为面向CLR(公共语言运行时)的程序代码,即托管代码。所有的托管代码都直接运行在CLR上,达到与平台无关的特性。

C#也是一种解释型的语言,因此C#编写的程序代码先通过C#编译器编译为一种特殊的字节代码MSIL(中间语言,Microsoft Intermediate Language,MSIL),然后通过CLR把MSIL编译为平台专用的代码并执行。具体过程如图2-23所示。

图2-23 C#代码执行机制

如图2-23所示,C#应用程序代码编译为MSIL中间语言后就可以获得.NET的平台无关性,这与目前应用非常广泛的Java程序编译为Java字节代码从而获得Java平台无关性的原理是一样的。

将中间语言MSIL编译成本地代码的过程为JIT编译,即时编译。它不是把整个应用程序一次编译完,而是编译它调用的那部分代码。代码编译过一次后,得到的本机可执行代码就存储起来,直到退出应用程序为止,在下次运行这部分代码时,就不需要重新编译了。这个过程比一开始就编译整个应用程序代码的效率要高得多,因为任何应用程序的大部分代码并不是在每次运行过程中都执行,节省了编译的开销。

在把代码编译为MSIL,再用JIT编译器把它编译为本机代码后,CLR的任务还没有全部完成。其实用.NET编写的代码在执行,即运行时是托管的。即CLR管理着应用程序,其方式是管理内存、处理安全性,以及允许进行跨语言调试等。

2.3.1 公共语言运行时(CLR)

.NET框架的核心是其运行库的执行环境,称为公共语言运行库或者公共语言运行时。前面介绍了关于CLR在程序执行过程中的作用,下面就简单介绍CLR的结构。我们提到过,C#是一门解释型的语言,因此这种语言很安全并且能够通过它的运行平台为其赋予更多的功能,比如类型安全、内存管理等。实际上C#的很多功能都是由CLR提供的。CLR的结构如图2-24所示。

图2-24 CLR结构

从图中可以看到,多线程支持(Thread Support)、向下兼容(COM Marshaler)、类型安全(Type Checker)、异常处理(Exception Manager)、垃圾回收(Garbage Collector),这些C#的特点都是由CLR来提供的。CLR最早被称为下一代Windows服务运行时(NGWS Runtime),是直接建立在操作系统层上的一个虚拟的运行环境,主要的功能是管理代码的运行。在.NET框架中,CLR之上是.NET的基类库(Base Class Library,BCL),这组基类库包括了从基本输入输出到数据访问等各方面,提供了一个统一的面向对象的、层次化的、可扩展的编程接口。

在CLR结构中,还提供了一个非常重要的功能,即公共语言架构CLI(Common Language Infrastructure,CLI)。CLI是CLR的一个子集,也就是.NET中最终对编译成MSIL代码的应用程序的运行环境进行管理的那一部分。

在CLR结构图中,CLI位于下半部分,主要包括类加载器(Class Loader)、实时编译器(IL to Native Compilers)和一个运行时环境的垃圾收集器(Garbage Collector)。CLI是.NET和CLR的灵魂,CLI为MSIL代码提供运行的环境,可以将使用任何语言编写的代码通过其特定的编译器转换为MSIL代码之后运行其上,甚至还可以自己写MSIL代码在CLI上面运行。

小提示

很多人都思考过应如何开始学习一种新的语言,对于一个有经验的编程人员来讲,这确非难事。但是对于一个对编写代码一无所知的人而言,如果从C#开始编程之旅,数目繁多的概念及新名词可能会令初学者不知所措。这时候需要注意学习顺序,任何一种编程语言的学习都是按照运行平台、语法、基类库直至各方面的应用这一顺序来进行的。但是在实际的学习中,它们之间并不是孤立的。

推荐的方法是:对运行平台和语法有了一个整体的认识后,在应用中学习各种基类库的用法。鉴于C#这一语言的特殊性,全面了解它的运行平台.NET必会使您的学习事半功倍。所以请记住第一章的.NET框架图和本章的CLR结构图,在以后的学习中,虽然可能不会明确地涉及到它们,但是在整个C#的学习和使用过程中,它们却是无处不在的。

2.3.2 微软中间语言(MSIL)

通过前面对C#程序执行机制的介绍,您应该明白,C#代码在执行前要编译成为中间语言MSIL,然后再通过JIT编译为本机代码。中间语言具有很多特性,如面向对象和使用接口、强数据类型、使用异常来处理错误、使用特性attribute、值类型和引用类型之间差别巨大,第二章我们也从C#语言层面对这两种类型的差别进行了介绍。

现在我们对中间语言MSIL有了一定的了解,您也不禁要问,MSIL究竟是什么样子的呢?下面我们就通过一段具体的程序来展示MSIL的“背后的故事”,让您对MSIL有比较直观的体验。代码2-36简单地展示了商品类的使用:

public partial class _Default : System.Web.UI.Page
  {
    class Product
    {
        private string name;
        public string Name
        {
          get
          {
              return Name;
          }
          set
          {
              Name = value;
          }
        }
    }
    protected void Page_Load(object sender, EventArgs e)
    {
        Product p = new Product();
        p. Name = "safegurad";
        Response.Write(p. Name);
    }
}

代码2-36

每个C#应用程序在执行的过程中都会产生.dll文件,可以通过诸如ILDASM等工具查看里面的MSIL代码。我们得到的中间语言代码如代码2-37所示:

.class auto ansi nested private beforefieldinit Product extends [mscorlib]
System.Object
{
    .field private string name
    .method public hidebysig specialname rtspecialname instance void.ctor() cil managed
{
  // 代码大小    7 (0x7)
    .maxstack 8
    IL_0000: ldarg.0
    IL_0001: call    instance void [mscorlib]System.Object::.ctor()
    IL_0006: ret
} // end of method Product::.ctor
  .method public hidebysig specialname instance string get_Name() cil managed
{
  // 代码大小    12 (0xc)
    .maxstack 1
    .locals init ([0] string CS$1$0000)
    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldfld   string attribute._Default/Product::name
    IL_0007: stloc.0
    IL_0008: br.s    IL_000a
    IL_000a: ldloc.0
    IL_000b: ret
} // end of method Product::get_Name
.method public hidebysig specialname instance void set_Name(string
'value') cil managed
{
  // 代码大小    9 (0x9)
    .maxstack 8
    IL_0000: nop
    IL_0001: ldarg.0
    IL_0002: ldarg.1
    IL_0003: stfld   string attribute._Default/Product::name
    IL_0008: ret
} // end of method Product::set_Name
} // end of class Product
.method public hidebysig specialname rtspecialname instance void
.ctor() cil managed
{
  // 代码大小    7 (0x7)
    .maxstack 8
    IL_0000: ldarg.0
    IL_0001: call    instance void [System.Web]System.Web.UI.Page::.ctor()
    IL_0006: ret
} // end of method _Default::.ctor
.method family hidebysig instance void Page_Load
(object sender,  class [mscorlib]System.EventArgs e) cil managed
{
  // 代码大小    38 (0x26)
    .maxstack 2
    .locals init ([0] class attribute._Default/Product p)
    IL_0000: nop
    IL_0001: newobj   instance void attribute._Default/Product::.ctor()
    IL_0006: stloc.0
    IL_0007: ldloc.0
    IL_0008: ldstr   "safegurad"
    IL_000d: callvirt instance void attribute._Default/Product::set_Name(string)
    IL_0012: nop
    IL_0013: ldarg.0
    IL_0014: call   instance class [System.Web]System.Web.HttpResponse [System.Web]System.Web.UI.Page::get_Response()
    IL_0019: ldloc.0
    IL_001a: callvirt  instance string attribute._Default/Product::get_Name()
    IL_001f: callvirt  instance void [System.Web]System.Web. HttpResponse::Write(string)
    IL_0024: nop
    IL_0025: ret
} // end of method _Default::Page_Load

代码2-37

上面的代码就是通过编译器产生的MSIL代码,这些代码看不懂没有关系,您只需明白在C#代码的执行过程中需要编译成中间语言。然后这些中间语言被JIT编译为专用于操作系统和目标机器的本机代码。这样不同的操作系统就可以执行这些应用程序了。

注意.NET的平台无关性目前还只是一种可能,因为到现在.NET只能用于Windows平台,但是微软的开发人员正在积极准备,使它真正可以用于其他平台,这在不远的将来也许就会实现。

喝杯咖啡,休息一下

关于.NET的运行机制、关于CLR和JIT等有着太多的内容,对于本书来说没有如此大的空间对这些基础内容一一进行介绍。但对于任何一位从事ASP.NET或者.NET平台下开发的软件开发人员来说,这些知识是必需的。同时,要在短时间内从初学者到完全理解这些内容也不是一件容易的事情,只有通过不断地练习,反复地查阅资料才能领会其中的精髓。相信您会成功!至于此刻,我再一次隆重地邀请您,休息一下吧,接下来我们一起来探讨一下.NET4.0中的新特性。编程大师如是说:

“当你有本事夺走我手中的这块水晶石时,就是你出师的时候了。” ————《编程之道》

2.4 .NET框架4.0新特性

.Net Framework 4.0的核心新增功能和改进主要有如下一些。

诊断和性能

.NET Framework的早期版本没有提供用于确定特定应用程序域是否影响其他应用程序域的方法,因为操作系统API和工具(例如,Windows 任务管理器)仅精确到进程级别。从.NET Framework 4.0开始,您可以获得每个应用程序域的处理器使用情况和内存使用情况估计值。

可监控各个应用程序域对CPU和内存的使用情况。通过托管承载API、本机承载API以及Windows事件跟踪(ETW),可提供应用程序域资源监控。在启用此功能后,将在进程的生存期内收集有关进程中所有应用程序域的统计信息。您现在可以访问ETW事件以用于诊断目的,从而改进性能。

垃圾回收

.NET Framework 4.0提供背景垃圾回收。此功能替代了以前版本中的并发垃圾回收并提高了性能。

代码协定

代码协定允许您指定方法或类型的签名没有单独表示的协定信息。新的System.Diagnostics.Contracts命名空间包含的类可提供一种与语言无关的方式以前置条件、后置条件和对象固定的形式来表示编码假设。这些协定利用运行时检查改进了测试,启用了静态协定验证并支持文档生成。

仅用于设计时的互操作程序集

您不必再提供主互操作程序集(PIA),即可部署与COM对象进行交互的应用程序。在.NET Framework 4.0中,编译器可以嵌入互操作程序集中的类型信息,仅选择应用程序(如外接程序)实际使用的类型。由公共语言运行时确保类型安全。

动态语言运行时

动态语言运行时(DLR)是一种新运行时环境,它将一组适用于动态语言的服务添加到CLR。借助于DLR,可以更轻松地开发要在.NET Framework上运行的动态语言,而且向静态类型化语言添加动态功能也会更容易。为了支持DLR,在.NET Framework中添加了新System.Dynamic命名空间。

表达式树由表示控制流的新类型(如System.Linq.Expressions..::..LoopExpression和System.Linq.Expressions..::..TryExpression)进行了扩展。动态语言运行时(DLR)将使用这些新增类型,而 LINQ不会使用。此外,System.Runtime.CompilerServices命名空间中还添加了多个支持.NET Framework基础结构的新类。

协变和逆变

现在,有多个泛型接口和委托支持协变和逆变。

BigInteger和复数

新的System.Numerics..::..BigInteger结构是一个任意精度Integer数据类型,它支持所有标准整数运算(包括位操作)。可以通过任何.NET Framework语言使用该结构。此外,一些新.NET Framework语言(例如F#和IronPython)对此结构具有内置支持。

新的System.Numerics..::..Complex结构表示一个复数,它支持使用复数的算术运算和三角运算。

元组

.NET Framework 4.0提供了用于创建包含结构化数据的元组对象的System..::..Tuple 类。它还提供了泛型元组类以支持具有1到8个组件的元组(即,从单一实例到八元组)。为了支持具有9个或更多组件的元组对象,提供了一个具有7个类型参数和任何元组类型的第8个参数的泛型元组类。

文件系统枚举改进

新的文件枚举方法可以提高访问大文件目录或循环访问大文件中的行的应用程序的性能。

内存映射文件

.NET Framework现在支持内存映射文件。可以使用内存映射文件编辑非常大的文件和创建共享内存以进行进程间通信。

64位操作系统和进程

使用Environment..::..Is64BitOperatingSystem和Environment..::..Is64BitProcess属性可以标识64位操作系统和进程。

打开基项时,可以使用 Microsoft.Win32..::..RegistryView 枚举指定32位或64位注册表视图。

2.5 伴您成长

2.5.1 您从本章学到了什么

本章通过对.NET集成开发环境Visual Studio2010的安装以及常用功能窗口的介绍,使您能够知道如何将这个开发平台安装到自己的计算机中,并可以对这个平台进行零距离的接触。同时通过对C#基础知识的介绍,以及通俗易懂的例子,使您能够在最短的时间内比较全面地掌握C#这门语言。

通过本章的学习您应该理解面向对象的思想,体会封装、继承和多态在面向对象中的核心地位。并能够将现实世界中的各种事物抽象为程序设计语言中的类。对初次接触C#的读者来说,这将是您进入微软C#编程世界的第一步;对于有相关经验的读者来说,这将进一步巩固您已掌握的C#知识,并尝试了解.NET 4.0以及C#4.0对编程带来的最新体验。

当然本章所讲解的都是非常基础的内容,目的是让您能够快速地建立C#编程思想,在Visual Studio 2010平台进行简单的程序开发。如同在汽车驾驶培训学校的宗旨一样,目的是让您能够将汽车发动,并能够进行入库、出库等操作,其他的诸如玩漂移、急速飙车等都是在技术熟练之后进行的提高。所以“万丈高楼平地起”,在您修炼了C#内功后,在具体的实践中一定能够建立起您自己的“帝国大厦”。

本章还对C#程序.NET的执行机制进行了比较详细的介绍,即C#程序代码首先编译为一种特殊的字节代码MSIL,然后通过CLR把MSIL编译为平台专用的代码并执行,从而在某种程度上满足了平台无关的概念。为了让您对MSIL有比较直观具体的印象,通过一个简单的例子展示了一段MSIL代码。虽然这些知识在实际的应用程序开发中不会直接涉及,但是作为一名.NET程序开发人员,对它的内部机制还是应该有一定的认识。这对掌握.NET框架以及编写高效的程序有内在的推动作用。

2.5.2 蛋糕加点奶油,咖啡加点糖

在对C#基础知识有了一个全面了解的基础上,我们一起来动手实践一下,只有这样才能将书里面的内容转移到大脑里面,成为记忆中的一部分。有句古话“纸上得来终觉浅,绝知此事要躬行”,说的就是这个道理。

首先回到我们的网上商店,在商店里面买了商品后,商家如何知道用户已经进行了购买操作呢?如何确定商品在通过“宅急送”之类的托运公司正确送到用户手中呢?所以这里就需要一个订单,显示用户购买的商品、交易日期、订单的状态(已经发货了或者仍然还在库房里面)等这些信息。

按照面向对象编程“一切皆为对象”的思想,需要将订单抽象为计算机中的类。大多数订单基本都包括这些内容:订单号、订单日期、订单状态、商品信息、商品总价以及用户的姓名、地址、联系方式等内容。因此根据订单包含的这些内容,首先需要将订单抽象为一个类,这些内容都属于订单的属性。同时由于在网上商店中,需要将订单里面的信息呈现在网页上面,所以类还需要一些方法来实现这样的功能,如显示订单中所有商品信息、计算订单中商品总价等。

首先需要将商品类添加进来,这样才能在订单类中使用商品类。由于在订单中需要计算总的商品价格,所以在商品类中需要添加一个表示商品数量的属性,如代码2-38所示。

class Product    //商品类
    {
        private int ProductID;    // 编码
        private string Name;     // 名称
        private string Description;  // 商品描述
        private double Price;     // 价格
        private int Number;      // 数量
        public Product(string name, double price,int number)// 构造方法,带参数
        {

} public double getPrice() //获得商品价格 {
} public int getNumber() // 获得商品数量 {
} public string showProduct() // 显示商品信息 {
} }

代码2-38

在继续下一步前,您需要将商品类的方法补充完整,里面的具体功能会根据您添加的代码而有所不同。

在订单中,至少包含一种以上的商品,所以在订单类中需要一个属性变量来存放订单中的商品。这里我们采用一个名为ArrayList的集合类来存放商品,它能够动态增加其所容纳的元素数量。使用这个集合前还需要引入一个命名空间,如下:

using System.Collections;

好,分析完后我们就开始设计订单类了,根据分析,订单类可以按照下面的方式进行构建,如代码2-39所示:

class Order  // 订单类
{  // 补充各个属性的类型
    private  orderId;      //订单号
    private  orderData;     // 订单日期
    private  totalPrice;    // 订单总价
    private  flag;        //订单状态
    private  userName;     //用户名称
    private  address;      //用户地址
    public ArrayList productList;  //商品列表 ArrayList
    public   TotalPrice()  //返回订单总价
    {

} public void getTotalPrice() // 计算订单所有商品的总价 {
} public ShowAllProduct() //显示订单中所有商品 { } }

代码2-39

在上面的订单Order中,已经将订单类的部分代码给出,您只需将订单类中的属性类型和返回订单总价、计算订单总价和显示订单中所有商品的方法实现即可。在继续看下面的内容之前,您可以自己构思,这些方法该如何实现。由于涉及到对ArrayList的操作,在这里我们也给出获得订单所有商品总价的getTotalPrice()方法的实现代码,如代码2-40所示。

public void getTotalPrice()  // 计算订单所有商品的总价
    {
        IEnumerator listEnum;
        listEnum = productList.GetEnumerator();  // 获得遍历器
        while (listEnum.MoveNext())   // 循环访问订单中所有的商品
        {
            Product product = (Product)listEnum.Current;
            double price = product.getPrice() * product.getNumber();
            totalPrice += price;
          }
    }

代码2-40

您可以根据上面的代码实现显示所有商品的方法ShowAllProduct()。

接下来,需要在程序中创建商品的实例并把这些实例放到订单中,然后再将订单中的商品显示出来,并统计出订单中的商品价格总和,如代码2-41所示。

protected void Page_Load(object sender, EventArgs e)
    {
        Order order = new Order();           // 创建订单实例
                            ;                // 创建两个商品实例
                            ;
        ArrayList pList = new ArrayList();   // ArrayList存放商品实例
        pList.Add(p1);                       // 商品分别添加到ArrayList中
        pList.Add(p2);
        order.productList = pList;           // 商品放入订单中
                            ;                // 计算商品总价
                            ;                // 显示订单中商品信息
                            ;                // 显示订单中商品总价
    }

代码2-41

根据程序里面的注释,您也需将上面的程序补充完整。

代码2-42为本节所介绍订单功能的参考程序,您可以与自己所写的程序进行对照学习。

class Product    //商品类
    {
        private int ProductID;    // 编码
        private string Name;     // 名称
        private string Description;  // 商品描述
        private double Price;     // 价格
        private int Number;      // 数量
        public Product(string name, double price,int number) // 构造方法,带参数
        {
            this.Name = name;
            this.Price = price;
            this.Number = number;
        }
        public double getPrice()   //获得商品价格
        {
            return Price;
        }
        public int getNumber()
        {
            return Number;
        }
        public string showProduct()  // 显示商品信息
        {
            string proInfo = "商品名:" "+ Name + "  价格: " + Price + " 数量: " + Number";
            return proInfo;
        }
    }
    class Order  // 订单类
    {
        private int orderId;      // 订单号
        private DateTime orderData;   // 订单日期
        private double totalPrice;   // 订单总价
        private char flag;        // 订单状态
        private string userName;    // 用户名称
        private string address;     //用户地址
        public ArrayList productList;  //商品列表
        public double TotalPrice()   //返回订单总价
        {
            return totalPrice;
        }
        public void getTotalPrice()  // 计算订单所有商品的总价
        {
            IEnumerator listEnum;
            listEnum = productList.GetEnumerator();  // 获得遍历器
            while (listEnum.MoveNext())   // 循环访问订单中所有的商品
            {
                Product product = (Product)listEnum.Current;
                double price = product.getPrice() * product.getNumber();
                totalPrice += price;
            }
        }
        public string ShowAllProduct()  //显示订单中所有商品
        {
            IEnumerator listEnum;
            string proListInfo = null;
            listEnum = productList.GetEnumerator();
            while (listEnum.MoveNext())
            {
              Product product = (Product)listEnum.Current;
              proListInfo += product.showProduct() + "<br/>";
            }
            return proListInfo;
        }
    }
    protected void Page_Load(object sender, EventArgs e)
    {
        Order order = new Order();         // 创建订单实例
        Product p1 = new Product("舒肤佳",4.5,2);  // 创建两个商品实例
        Product p2 = new Product("海飞丝",17.5,3);
        ArrayList pList = new ArrayList();     // ArrayList存放商品实例
        pList.Add(p1);      // 商品分别添加到ArrayList中pList.Add(p2);
        order.productList = pList;         // 商品放入订单中
        order.getTotalPrice();           // 计算商品总价
        Response.Write(order.ShowAllProduct());    // 显示订单中商品信息
        Response.Write(order.TotalPrice());      // 显示订单中商品总价
    }

代码2-42

2.5.3 下一步做什么

学习了本章后,我们就可以采用C#进行程序开发了。但是目前大部分基于互联网的应用程序都涉及到对数据库的交互,比如网上商店中商品信息的管理,如添加、删除和修改等都是对数据库进行操作。接下来,本书将要对数据库的知识进行介绍。通过与数据库的结合,向您展示功能更加强大、更加完美的ASP.NET开发环境。

关于生活

时间总是过得那么匆忙,一天的光阴就这样结束了。

结束了一天的学习之后,也许心中还有很多的疑问,但身心已经疲惫,暂时地关上这扇门吧,也不要去过多地思考,做您想做的事,放松心情,养精蓄锐。

深夜,独自回到家中,音乐微微地响起,拿起桌上的书,细细品读,平淡的生活就是如此美妙。

对于我来说,踢球和听音乐是最能让我轻松下来的事,您也寻找这样的业余爱好吧,相信会受益匪浅。