Visual C# 2010入门经典

978-7-115-25161-9
作者: 【美】Scott Dorman
译者: 张劼
编辑: 傅道坤龚昕岳
分类: C#

图书目录:

详情

Visual C# 2010将C++的灵活性和强大功能与Visual Basic的简单性融于一体。本书全面阐述了使用Visual C# 2010开发应用程序的基本知识。

图书摘要

计算机技术译林精选系列

Visual C# 2010入门经典

[美] Scott Dorman 著

张劼 译

人民邮电出版社

北京

C#是专门为.NET Framework开发的语言,融C++的灵活和强大与Visual Basic的简单于一身,已成为在.NET平台上进行开发的首选编程语言。本书简要而全面地介绍了C#和.NET Framework,可帮助程序员迅速进入Visual C# 2010编程领域。

全书分 5部分,共 24章。第一部分介绍.NET Framework、C#面向对象编程功能以及C#类型和事件的基本知识;第二部分阐述基本的编程知识,包括循环、字符串、正则表达式、数组和集合以及异常、泛型、匿名函数(Lambda)和查询表达式(LINQ);第三部分演示如何与文件系统交互、创建和查询XML文档以及使用数据库;第四部分阐述了应用程序调试、WPF 应用程序、数据绑定和验证以及 Web 应用程序;第五部分简要地介绍了特性编程、动态类型、内存组织、垃圾收集、线程、任务并行库(TPL)、并行LINQ(PLINQ)、Silverlight、PowerShell和实体框架等。

本书是为刚涉足.NET的程序员和想学习C#的.NET程序员编写的,如果您刚开始学习编程,本书也会有所帮助。

 

Soctt Dorman被Microsoft授予最有价值的C#专业人员(C# Most Valued Professional)称号,以表彰他为C#社区做出的重大贡献。从记事起,Scott就一直与计算机打交道,他于1993年开始从事计算机方面的工作,并于2001年开始使用.NET和C#。当前,他主要致力于使用Microsoft .NET技术开发商业软件,还负责一个关注软件架构的用户组、在各种会议上(包括 Microsoft TechEd 和社区资助的 Code Camp)发表演讲、定期在 The Code Project 和StackOverflow等在线社区发表文章。Soctt还就.NET Framework和C#技术撰写博客,其博客地址为http://geekswithblogs.com/sdorman。

10年前,在Microsoft公司41号楼二层的一个小型会议室,一个设计小组开发了一种全新的编程语言——C#。该语言的指导原则是简单、熟悉、安全和实用。当然,需要在这些指导原则之间折衷,任何一个原则都不是绝对的。这些设计师希望这种语言具有如下特点:易于理解又不过于简化;C++和Java程序员熟悉但不完全抄袭;安全但限制性不过强;实用但不完全放弃设计原则。

经过长时间的思考、设计、开发、测试和文档编写,向公众发布了C# 1.0。这是一种简单易懂的面向对象语言,对其设计的很多方面都做了仔细考量,确保对象可组织成独立的组件,但该语言的基本概念都是从20世纪70年代前的面向对象语言和过程性语言借鉴而来的。

还是在二楼的那间会议室,设计小组每周相聚 3次,在C# 1.0打下的坚实基础上继续工作。通过与街对面的Microsoft Research Cambridge和CLR小组通力合作,拓展了类型系统,让泛型类型和泛型方法支持参数化多态;还增添了迭代器块(在其他语言中称为生成器),这使得创建可迭代集合和匿名方法更容易。泛型和生成器(generator)最初出现在 20 世纪 70年代和80年代的CLU和Ada等语言中,而在现有方法中嵌入匿名方法的理念可追溯到20世纪50年代的计算机科学。

相对于之前的版本,C# 2.0有巨大进步,但设计小组并不满足。他们还是每周 3次相聚在那间二楼的会议室,但考虑的是基础性问题。传统的过程性编程语言在执行基本算术运算方面做得很好,但当今开发人员面临的问题比计算一系列数字的平均值要复杂得多。设计小组认识到,为操作数据,程序员需要以复杂方式组合使用相对简单的操作,这些操作通常包括对数据集合进行排序、筛选、分组、连接和投影。句法模式(syntactic pattern)准确地描述了这些操作,这种概念可追溯到Haskell等函数式语言,但也适用于C#等更严格的语言。LINQ(语言集成查询)应运而生。

设计小组每周都在最初那间会议室相聚 6 小时,这项活动持续了 10 年;随后,为与不在现场的小组成员召开电话会议,会议地点移到了五楼。回顾10年来的历程,设计小组找出了C#仍没有很好解决的问题、不尽如人意的地方等。鉴于对与现代动态语言和遗留对象模型互操作的需求日益增加,开发小组在C# 4.0中引入了一些新功能,如动态类型。

在这里简要地回顾一下C#的发展历程是个不错的主意,但本书显然没有以编年史的方式介绍C#,这很好!编写入门书籍时,作者通常按读者的学习顺序组织材料。在大多数情况下,这与功能被引入到语言中的顺序并不相同。对于本书,我特别欣赏的一点是,Scott按合乎逻辑的方式介绍了各种概念,从最基本的算术运算到非常复杂的概念。另外,他选择的示例很真实,激发读者去思考,同时又足够简单,只需几个段落就能说清楚。

这里重点介绍了C#的发展历程,但语言的发展历程远不是全部。语言只是一个工具,让您能够利用运行库和框架库的强大功能,而运行库和框架库本身就是复杂而庞大的主题。对于本书,我特别欣赏的另一点是,它不仅关注C#语言本身,还在介绍C#概念后,阐述如何使用基类库中常用的类型提供的强大功能。

从前面的 C#简史可知,即使只考虑 C#语言本身,需要学习的东西也很多。我使用 C#已有10年,担任C#设计师的时间也有5年,但每天仍能学到有关C#的新知识和新的编程技巧,但愿本书能带领您踏上持续10年的编程和学习之旅。至于C#设计小组,我们仍每周相聚6小时,努力找出未来的发展方向。但愿能够成功!

Eric Lippert

2010年3月于华盛顿西雅图

1998年12月底,Microsoft着手推出一种全新的开发平台,旨在提供一种创建和运行下一代应用程序和Web服务的全新方式。这种新平台名为.NET Framework,于2000年6月发布。

.NET Framework将原有Windows接口和服务融合到单个应用程序编程接口(API)下,并将众多新出现的行业标准(如简单对象访问协议[SOAP])和众多原有的Microsoft技术(如Microsoft组件对象模型[COM和COM+]和Active Server Pages[ASP])加入其中。除提供一致的开发体验外,.NET Framework还提供了最大的类库之一,让开发人员能够重点关注应用程序逻辑,而不是常见的编程任务。

最后,应用程序在托管运行环境中运行,而这种环境自动处理内存分配并提供了沙箱(限制访问),这减少甚至消除了众多常见的编程错误。

10年后的今天,.NET Framework在不断发展,它支持新的技术和行业标准,支持动态语言,提供了更多的类。在2008年举行的Microsoft专业开发人员大会(PDC)上,主题之一是“让简单的事情容易,让艰难的事情可能”。.NET Framework第1版秉承了这种宗旨,而每个新版本都在不断向这个目标迈进。

C#编程语言是Anders Hejlsberg、Scott Wiltamuth和Peter Golde为.NET Framework开发的,于 2000年 7月首次发布。鉴于C#是专门为.NET Framework开发的,很多人将其视为.NET Framework的御用语言。作为编程语言,C#借鉴了Delphi 5、C++和 Java 2的语法和主要功能,它是一种类型安全的面向对象通用语言,可用于编写任何类型的应用程序。随着.NET Framework不断发展,C#也在不断发展,以适应.NET Framework的新变化并引入新功能,从而让简单的事情容易,让艰难的事情可能。

虽然.NET Framework支持的语言有50多种,但C#始终是最受欢迎的现代通用语言之一。

针对的读者及组织结构

本书是为刚涉足.NET的程序员和想学习C#的.NET程序员编写的。如果您刚开始学习编程,本书也会有所帮助,但它并非编程入门。本书旨在让读者尽快学会如何使用C#完成编程任务,进而提高编程效率。它全面概述了C#语言,重点是其功能如何帮助您解决问题,旨在让您对C#有最全面的认识。

本书分5部分,每个部分都关注C#的不同方面:从简单的基本知识到高级主题,因此建议按顺序阅读。

第一部分“C#基础”介绍.NET Framework、C#的面向对象编程功能以及C#类型系统和事件的基本知识。

第二部分“C#编程”阐述基本的编程知识。您将学习如何执行循环以及如何使用字符串、正则表达式和集合;接下来,将转向更高级的主题,如异常管理和泛型;最后,将介绍匿名函数(Lambda)、查询表达式(LINQ)以及如何与动态语言交互。

第三部分“使用数据”演示如何与文件系统和流交互、创建和查询 XML 文档以及操作数据库。

第四部分“使用Visual Studio创建应用程序”首先简要地介绍了Visual Studio 2010和应用程序调试,然后创建了一个使用数据绑定和数据验证的Windows客户端应用程序,最后阐述了如何创建Web应用程序。

第五部分“深入探索”简要地介绍了特性编程、动态类型和语言互操作性等高级概念。您将首先学习.NET Framework如何组织内存、垃圾收集器如何工作、.NET Framework提供的确定性终结机制等基本知识;接下来,您将学习如何使用多个线程和并行处理;最后,您将了解Microsoft在.NET Framework的基础上提供的一些新技术,如Silverlight、PowerShell和实体框架。

本书提供了大量的示例,旨在演示现实世界的问题以及如何使用 C#和.NET Framework解决这些问题。在第四部分,还利用前3部分介绍的技能从空白开始创建了一些完整的应用程序。

本书使用的约定

本书使用了一些设计元素和约定,让读者能够充分利用和参考书中的信息。

注意:提供有用的信息,读者可以马上阅读,也可阅读完当前话题后再阅读。

提示:突出那些可让编程更有效的信息。

警告:提醒读者注意可能在特定情况下发生的问题或副作用。

为突出新术语,使用粗体显示它们。

另外,本书对代码和其他文字使用不同的字体。代码使用等宽字体,占位符(表示需要在代码中输入实际单词或字符)使用斜体和等宽字体,需要用户输入的文本用粗体表示。

书中的有些语句过长,一行容纳不下。在这种情况下,使用续行字符指出下一行是当前语句的一部分。

最后的思考

在我使用过的编程语言中,Microsoft .NET Framework和C#始终是最强大、最别致的,让您能够开发下一代杀手级应用程序。阅读本书后,您不可能成为C#专家,但希望您能得心应手地使用.NET和C#创建应用程序。

第1章 .NET Framework和C#

第2章 理解C#类型

第3章 理解C#类和对象

第4章 继承、接口和抽象类

第5章 创建枚举类型和结构

第6章 事件及其处理

本章将介绍:

了解.NET Framework;

并行计算平台(Parallel Computing Platform);

动态语言运行时(Dynamic Language Runtime)的作用;

C#语言概述;

C#语言的语法;

编写第一个C#程序。

学习新语言与学骑自行车或学开车类似,必须首先学习基本知识,让您有信心学会更复杂的操作。理解基本原理并对实现目标充满信心后,目标便突然间变得不那么遥不可及了。阅读本章后,读者将对如下方面有基本认识:.NET Framework;.NET Framework组件以及这些组件之间的关系;如何创建应用程序。您将学习 C#语言的基本知识以及如何编写第一个.NET应用程序。

.NET Framework提供了一些工具和技术,让开发人员能够以独立于语言和平台的方式创建并运行下一代应用程序和Web服务。它提供了一个庞大的类库,这个类库支持众多常见的任务,简化了众多艰难的任务,让您能够将更多的时间用于解决手头的任务:以尽可能高效的方式满足业务需求。

.NET Framework具有如下功能。

提供了一个运行环境,以简化软件开发过程,降低版本冲突的可能性。

确保代码的安全执行。

基于行业标准进行所有通信,从而能够与非.NET代码集成。

让应用程序的开发过程独立于语言和平台,确保各种应用程序的开发体验都是一致的。

提供了一个运行环境,尽可能消除了脚本语言或解释型语言的性能问题。

为实现上述目标,.NET Framework包含 4个组件。

第一个组件是公共语言运行时(Common Language Runtime),可将其视为 .NET Framework 的核心。就像心脏让机体能够正常运行一样,公共语言运行时提供了低级核心服务供应用程序使用,并负责管理应用程序的代码。针对.NET Framework编写的代码称为托管代码(managed code),而其他代码称为非托管代码(unmanaged code)。

.NET Framework的第二个组件是类库,它包含大量可重用的类(类型),可用于开发您能够想象得到的任何应用程序。

注意:Framework类库

Framework类库包含4000多个公有类,是当今最大的类库之一。

.NET Framework通过类库在公共语言运行时中支持并行编程,并在并行计算平台中提供了诊断工具。并行计算平台是.NET Framework的第三个组件,让您能够以自然而简单的方式编写高效的可扩展代码,以充分利用多个处理器。

.NET Framework 的第四个组件是动态语言运行时,它建立在公共语言运行时的基础之上,向诸如IronRuby和IronPython等动态语言提供语言服务。与诸如C#等通用语言相比,动态语言更擅长执行某些任务,而动态语言运行时让您能够根据业务需求选择最合适的语言。不仅如此,动态语言运行时还让非动态语言(如C#)能够以简单而一致的语法使用动态对象,而不管这些对象源自COM、IronRuby、IronPython还是JavaScript。

提示:动态语言是什么?

在诸如 C#等使用静态类型的语言中,由编译器确定类型安全,如果无法确定,就将生成错误。在动态语言中,不会尝试确定类型安全。另外,大多数动态语言在运行阶段执行更复杂的类型操作,如判断方法重载是否正确,而C#在编译阶段做出这种判断。

实际上,对于静态类型语言在编译阶段执行的所有任务,动态语言都将在运行阶段执行。这包括在运行阶段使用eval或repl循环生成代码,以修改运行对象的状态。因此,动态语言提供了极大的灵活性,常用作脚本语言。

一些常见的动态语言包括Jscript、JavaScript、Python、IronPython、Ruby和IronRuby。

针对.NET Framework编写的代码称为托管代码,而根据这些代码生成的应用程序称为托管应用程序。托管应用程序运行时,将自动承载( host )相应的公共语言运行时。.NET Framework 不仅提供了大量的运行时宿主,还提供了让您能够编写宿主的工具。这样,诸如Internet信息服务(IIS)和Microsoft SQL Server等非托管应用程序也可承载公共语言运行时备份,从而能够利用托管功能和非托管功能。

图 1.1说明了.NET Framework的各个组件与应用程序、操作系统和非托管应用程序之间的关系。

图1.1.NET Framework环境

公共语言运行时(Common Language Runtime,CLR)是.NET Framework的核心,提供了一个统一类型系统和一个托管运行环境,它们为开发和执行独立于语言和平台的应用程序打下了坚实的基础,还有助于消除(至少是减少)众多常见的编程错误。

1.通用类型系统

前面介绍的统一类型系统称为通用类型系统(Common Type System,CTS),让所有.NET语言共享相同的类型定义,这样便可以一致的方式操作这些类型。这有助于确保正确地编写应用程序,其原因如下。

消除了将不兼容的数据赋给类型的可能性。

每种.NET语言的类型描述都相同,而不管用于定义该类型的语言是什么。

以一致的方式操作类型。

提示:本章后面将简要地介绍类型,而第2章将更详细地讨论它们。

注意:类型安全和CTS

通用类型系统和公共语言规范为.NET Framework类型安全打下了坚实的基础。

这让.NET Framework能够以一致的方式倡导类型安全,而无需强制执行类型安全。强制执行类型安全的任务留给了语言编译器和稍后将介绍的虚拟执行系统。

通用语言系统以独立于语言的方式定义类型,因此它必须考虑这些语言之间的差别。通用语言系统提供了一个最基本的规则集,所有.NET语言及其编译器都必须遵守,这个规则集称为公共语言规范(Common Language Specification,CLS)。这种通用定义让语言集成成为可能:使用另一种语言中定义的类型,就像它是在当前语言中定义的一样。

提示:CLS遵从性

几乎Framework类库中的所有类都符合CLS,因此任何.NET语言都可使用这个类库。如果要开发自己的类库,那么建议您确保这些类也符合CLS,这样它们才能得到最广泛的使用。

2.通用中间语言

通用类型系统和公共语言规范有助于实现独立于语言和平台的目标,但是如果编译器生成的可执行目标代码依赖于硬件平台,那么它们带来的好处将消失殆尽。为解决这个问题,将托管代码进行了部分编译,以生成使用一种低级语言的代码,这种语言称为通用中间语言(Common Intermediate Language,CIL)。通用中间语言类似于汇编语言,由表示高级语言代码的低级指令组成。

程序集(assembly)是部分编译单元(包),包含CIL指令并为定义类型提供了逻辑边界。由于程序集是部分编译的,因此可以是32位的,也可以是64位的,这取决于操作系统和硬件。这意味着托管应用程序是独立于平台的,且无需重新编译或添加特殊指令就可以充分利用硬件技术。

3.虚拟执行系统

公共语言运行时的另一个重要组成部分是托管运行环境,这种环境称为虚拟执行系统(Virtual Execution System,VES),它负责处理应用程序所需的低级核心服务。就像 Java应用程序需要有 Java 虚拟机(JVM)才能运行一样,托管应用程序也需要有 CLR(具体地说是VES)才能运行。

.NET应用程序启动时,由VES负责加载并执行CIL代码,并管理应用程序所需的内存分配。换句话说,VES提供服务和基础设施,从而消除平台和语言差异带来的影响。

在加载和编译过程中,VES执行各种验证,以确保文件格式、程序集元数据和CIL是一致的,并确保CIL指令不能非法访问内存。这样,应用程序将只能访问它被授权访问的内存和资源。可将这种受限的环境视为沙箱(sandbox)。

既然VES提供了运行环境并执行包含CIL的程序集,那么这些程序集是解释型还是编译型的呢?别忘了,.NET Framework的目标之一是提供这样的运行环境:最大程度地减少甚至消除脚本(解释型语言)的性能问题。这意味着CIL代码是编译型的,但编译是在什么时候完成的呢?

VES 提供的服务之一是即时(Just-In-Time,JIT)编译器。即时编译指的是在运行阶段将经过部分编译的CIL代码转换为可执行的目标代码(本机代码)。

提示:即时编译

即时编译过程被称为jitting,而JIT编译器被称为jitter。

通过以这种方式编译代码,.NET Framework获得了比传统解释型语言高得多的速度。相对于常规(静态)编译,即时编译也有优势,因为它可在运行阶段实施安全保障,还可在运行阶段重新编译以进一步优化代码。.NET Framework JIT编译器进行了高度优化,以便将CIL代码编译成高效的目标代码;它在需要时运行,并缓存编译后的代码供以后使用。

4.内存管理和垃圾收集

在众多非托管编程语言中,内存管理是个经典问题,它可能导致一些常见的错误。使用这些编程语言时,开发人员负责在正确的时间分配和回收内存。.NET Framework让VES自动分配和回收内存,从而解决了这个问题。

这种自动内存管理称为垃圾收集,正是它让C#(和其他.NET语言)成为一种垃圾收集语言。垃圾收集让您无需过多地为释放不需要的内存而操心,它避免了众多常见的编程错误,让您创建的应用程序更稳定,并可重点关注应用程序所需的业务逻辑。

即使有了自动内存管理,理解垃圾收集器如何与程序以及您创建的类型交互也很重要。深入讨论垃圾收集超出了本书的范围,但第22章将简要地讨论它。

虽然CLR是.NET Framework的核心,但真正赋予它力量的是Framework类库(FCL)。这个类库类似于Java类库、C++标准模板库(STL)、Microsoft活动模板库(ATL)、Microsoft Foundation Class(MFC)、Borland对象窗口库(OWL)及其他类库。

与这些类库一样,FCL也包含大量可重用的类型,这简化了众多常见的编程任务,从而提供了开发效率。

图1.2列出了FCL中的一些类型,并按功能对它们进行了编组。

图1.2 Framework类库

注意:Framework类库

Framework类库充分说明了.NET Framework让简单的事情容易、让艰难的事情可能的特征。

虽然不使用 FCL 提供的类型也可以创建应用程序,但实际上不可能这样做。

在最底层是基础类库(Base Class Libraries,BCL),它们充当所有.NET语言的标准运行环境,提供的类型可表示CLR类型、集合、流和其他数据结构,可用于操作字符串、执行基本的文件存取和各种其他操作。BCL总共有172 种公有类型,而Ecma-335 标准(Common Language Infrastructure (CLI), 4th Edition)定义的标准库总共有 331种公有类型。

提示:Ecma是什么?

Ecma International是一个创建于 1961年的国际标准协会,旨在帮助制定信息通信技术和消费电子产品的使用标准。

2001年 12月 14日,C#语言规范(C# Language Specification)和通用语言基础设施(Common Language Infrastructure,CLI)获批成了Ecma标准。

CLI是公共语言运行时(Common Language Runtime)的开源版本。在Ecma 的鼓励下,开发了 C#和.NET Framework 的多个开源版本,包括DotGNU和Mono。其中最著名的可能是Mono,它提供的.NET开发平台实现可用于Linux、BSD、UNIX、Mac OS X、Solaris和Windows操作系统。

正是Ecma提供的开源标准以及诸如DotGNU和Mono等项目让开发技能和应用程序几乎可用于任何平台。

FCL 中的其他类关注的都是特定功能,如提供数据访问、XML 支持、全球化支持、诊断、配置、联网、通信、业务流程支持、Web应用程序和Windows桌面应用程序等。

命名空间

鉴于.NET Framework类库包含数千个类,需要采取某种方式避免类名混淆,并提供一种方便的层次编组机制。为此,.NET Framework采用了命名空间的概念。命名空间不过是一系列类型,对类型的可见性没有任何影响。命名空间可包含多个程序集。.NET Framework使用层次型命名空间提供了一个渐进型框架,打造出了一个功能强大而又易于使用的开发平台。

注意:命名空间和类型名

命名空间使用点分式语法表示层次结构,每层之间都用句点(.)分隔。

在类型的完整名称中,以最右边的句点为分界点,左边是命名空间,而右边是类型名。例如, System.Printing.PrintDriver 是位于命名空间System.Printing中的类型PrintDriver的完整名称。

然而,只有.NET编程语言支持命名空间。在CLR中,类型总是用完整名称标识,其中包含类型的名称及其所属的命名空间。

.NET Framework类库包含大约 4000个命名空间,但有些命名空间您一辈子也不会与它打交道。随着您越来越熟悉这个类库,您将发现有些命名空间是您较常用的,而它们可能与您的同事常用的命名空间不同。

表1.1列出了最常用的命名空间。

表1.1 常用的命名空间

一直以来,都可使用托管和非托管代码编写多线程和异步应用程序,但要正确地编写这样的应用程序始终很困难。.NET Framework 4.0提供的并行计算平台简化了这种应用程序的编写工作。这是一种全新的编程模型,适用于托管代码和非托管代码。它提高了抽象程度,让您无需考虑较低级的概念,如线程和锁定。

提示:有关多线程和并行编程的更详细信息,请参阅第23章。

对于托管代码,并行计算平台提供了常见循环指令的并行实现和LINQ to Objects的并行实现,还新增了可避免死锁的线程安全集合。Visual Studio 2010新增了一些诊断工具,如并行分析器和处理器迁移分析,让您能够轻松地调试和优化代码。

并行计算平台简化了这样的机制:编写可高效利用多个处理器的代码。要确定什么样的代码适用于并行计算,仍需要进行分析,当您考虑如何解决特定问题时,这将改变您的思考方式。第23章将简要地介绍并行计算平台的上述方面。

动态语言运行时(Dynamic Language Runtime,DLR)是.NET Framework 4.0新增的,这是一个为动态语言提供服务和支持的运行环境。

DLR 建立在公共语言运行时的基础之上,这意味着动态语言可与其他.NET 语言集成。DLR 还让现有的静态语言(如 C#)能够支持动态功能,这让它们能够以一致的方式使用动态对象,而不管这些对象来自何方。

提示:第21章将详细介绍如何集成动态语言。

有了 DLR 后,便可支持动态语言,而静态语言也可使用动态功能。这样,开发人员就可根据要解决的问题选择最合适的语言,且创建的动态代码可供其他开发人员和.NET语言轻松地使用。

如果您是C、C++或Java程序员,将很快熟悉C#,因为其语法与这些语言类似。如果您熟悉Visual Basic(运行在.NET Framework上的任何Visual Basic版本,而不是Visual Basic 6.0或更早的版本),可能对C#语法感到陌生,但应熟悉Framework类库。如果您从未使用过上述任何语言,也将发现与众多其他语言相比,使用C#进行编程更容易,因为它语法优雅,类库庞大。

提示:语言灵感

作为一种语言,C#借鉴了众多不同的语言,以形成其语法和主要功能,这包括Delphi 5、C++和 Java 2。

泛型系统(第11章将更详细地介绍)是从Eiffel和Ada那里借鉴而来的;而LINQ和Lambda表达式评估(参见第12章)主要是从Haskell和Lisp那里借鉴而来的。

C#还添加了一些来自动态语言(如Ruby)和函数语言(如F#)的功能。

与众多现代编程语言一样,C#也是一种面向对象的语言,全面支持面向对象编程概念,如继承、多态、封装和抽象。另外,C#还支持面向组件编程,这让您能够指定自带文档的独立功能单元(组件),为此只需提供一个模型,它包含属性、方法、事件以及有关组件的元数据。C#本身就支持这些概念,这使得创建和使用组件的过程非常自然。如果您不熟悉这些概念,请参阅第3章。

C#提供的语言功能让开发人员能够利用 CLR 的改进。垃圾收集功能自动管理内存;异常处理提供了一种可扩展的结构化方法,让您能够检测错误并从错误中恢复。作为一种类型安全语言,下述情况根本不可能发生:变量未初始化、非法访问内存、将一种类型的数据存储到只能接受另一种类型的地方。

另外,C#的语言功能和语法设计还减少了您需要编写的代码量,这让代码更简单,降低了发生常见错误的可能性。在有些情况下,这种改进不过是改变语法:简化复杂或容易出错的语言功能,让它们更容易使用和理解;在其他情况下,这种改进提供了更高级的功能。

每个新的C#版本都做了改进:新增语言功能、改进语法,致力于让简单的事情容易、让困难的事情变得可能、让糟糕的事情难以发生。随着C#不断新增功能,简单的事情变得更容易了,困难的事情变得容易了,以前不可能的事情变得可能了。

在C#中,类型是对值的描述。每当您需要一个值时,就需要一种类型。正如前面介绍通用类型系统时指出的,类型定义了允许的值以及这些值支持的操作。在C#中,每个值都由其所属的类型全面描述,且是其所属类型的一个实例。所谓全面描述,指的是类型明确地指定了值的表示方式以及可对它执行的操作。

C#类型分两类:值类型和引用类型。值类型描述的值是完全独立的,这包括数值类型、枚举类型和结构;引用类型存储指向值的引用,而不是值本身。

提示:第2章更深入地探讨了值类型和引用类型之间的差别。

C#提供了众多预定义的值类型和几种预定义的引用类型,您还可以创建用户定义的类型。在接下来的几章中,将更详细地探讨值类型和引用类型之间的差别以及如何创建用户定义的类型。但就目前而言,只需知道一个最重要的差别:值类型包含实际值,而引用类型包含指向实际数据的引用。

语句是一条完整的程序指令,必须以分号(;)结尾。每条语句中只能包含一条指令,这看起来限制性很强,但C#还提供了语句块,这是一组用大括号括起的语句。在可以使用单条语句的任何地方都可使用语句块。

由于语句以分号结尾,因此可使用空白(如空格、制表符和换行符)调整代码的排列方式。最佳的方法是采用一种简单而一致的风格(如果您的公司或团队没有这样的风格),让代码更容易阅读和维护。

警告:空白

虽然编译器通常忽略空白,但是类型声明、标识符和其他关键字之间的空白很重要。如果没有空白,编译器将无法识别关键字。

表达式的结果为值。如果将语句视为操作,那么表达式就是计算。结果为布尔值(true或false)的表达式常用于判断条件是否满足,这种表达式称为布尔表达式

对于变量,最简单的定义是它表示一个存储位置,其中的值可随时间流逝而变化。最常见的变量是局部变量和字段,它们都可通过指定类型、标识符和可选的初值来定义:

int a;

int b = 1;

如果要声明多个类型相同的变量,就可将声明合并在一起,如下所示:

int a, b;

在限定作用域(如方法)内声明的变量称为局部变量,只能在该作用域内通过名称访问它。

注意:作用域、声明空间和寿命

可将作用域视为容器,只有在该容器内才能通过非限定名合法地访问变量。这不同于声明空间,在声明空间内,不允许有两个名称相同的标识符。如果说作用域指定了您在哪里能够使用某个名称,那么声明空间就指出了该名称在哪里是唯一的。

变量的寿命与其作用域紧密相连,它决定了变量在多长时间内可用。只要当前执行的代码还位于变量的作用域内,该变量就可用。

第3章将更详细地介绍作用域和声明空间。

字段是在限定作用域内声明的变量,它要么与类型本身相关联,要么与类型的一个实例相关联。在前一种情况下称为静态变量(可将其视为一种全局变量);在后一种情况下则称为实例变量。使用局部变量和字段之前,必须将其初始化;另外,只有在这些变量的声明所属的代码块内才能访问它们。

程序清单1.1是一个名为Color的类型,其中包含私有实例字段red、blue和green,还包含公有静态字段White、Red、Blue和Green。

程序清单1.1 Color类

使用静态字段之前,需要对其进行初始化,但是此后无法保证它们的值不会变化。为了声明初始化后就不能修改的字段,可以创建只读字段。

提示:有关只读字段的更详细信息,请参阅第3章。

程序清单1.2列出了Color类中被修改的代码行。

程序清单1.2 使用只读字段的Color类

常量表示在编译阶段可计算的值。常量与类型本身相关联,就像是静态的。与变量一样,常量可在限定作用域内声明,也可以是全局的;与变量不同的是,必须在声明常量时对其进行初始化。

提示:字面值和魔数

字面值通常是有特殊含义的数值,并在代码中直接指定。随着时间的推移,可能忘记字面值的含义,导致相应的代码难以维护。因此,这些字面值常被称为魔数(magic numbers)。通过使用常量而不是字面值,可将其含义保留下来,让代码的含义不言自明。

对于下述函数中的数字,您花了多长时间才弄明白其含义呢?

如果使用常量重新编写该函数,其中的魔数的含义将非常明显:

在这个示例中,299792458是一个字面值,因此可将其视为一个魔数。您可能猜到了,应使用常量而不是字面值,因为与数字相比,常量的名称可更好地说明其含义,还可确保其值不会变化。

声明变量或常量的语句通常称为声明语句,可位于代码块的任何地方。

声明变量、字段或常量时,必须指定数据类型并提供有意义的名称,后者称为标识符。

标识符必须遵循如下规则。

只能包含字母(大写和小写)、数字和下划线。

标识符必须以字母或下划线打头,但对于公有标识符,以一个或多个下划线打头是一种糟糕的做法,应避免这样做。

在给定的声明空间内,标识符必须是唯一的。

标识符是区分大小写的。

选择标识符时,还应遵守的其他指导原则如下。

标识符应易于阅读。

标识符应使用缩写。

标识符应提供尽可能丰富的含义。

在C#中,标识符是区分大小写的。推荐的命名约定建议如下:对于变量名和参数名,使用Camel大小写规则,该规则要求除第一个单词外,其他单词的首字母都大写,如bookTitle;对于方法名和其他标识符,使用Pascal大小写规则,该规则要求每个单词的首字母都大写,如BookTitle。

提示:Camel和Pascal大小写规则

采用Camel大小写规则时,标识符中的大写字母看起来向驼峰,这种大小写规则因此而得名。Pascal大小写规则因其被Pascal编程语言广泛采用而得名(Turbo Pascal语言最初是由Anders设计的)。

Microsoft不再推荐使用Hungarian表示法,也不推荐使用下划线分隔单词,这两种表示法在其他语言中很常用。

如果您熟悉其他区分大小写的语言,如 C、C++或 Java,就会觉得这很正常;如果您以前使用的是不区分大小写的语言,如Visual Basic,可能需要一段时间才能适应。所幸的是, Visual Studio 2010代码编辑器提供的功能可让这种过渡更容易。

由于标识符定义了特定元素的名称,因此C#也需要使用标识符向编译器和您表示特殊含义,它保留了一些标识符供自己使用,这些标识符称为关键字

在C#中,有77个标识符在任何情况下都属于关键字,如表1.2所示。

表1.2 C#关键字

续表

另外,还有 24 个关键字称为上下文关键字,它们仅在特定情况(上下文)中有特殊含义。离开该上下文后,这些关键字就可供您使用,但为避免混淆,应尽量避免使用。表 1.3列出了这些上下文关键字。

表1.3 C#上下文关键字

Visual Studio 是一个功能齐备的集成开发环境(Integrated Development Environment IDE),由众多工具组成。这些工具的目的相同:让您能够创建新颖的下一代应用程序。Visual Studio的核心包括功能强大的代码编辑器、语言编译器(支持Visual Basic、C#和 F#)和调试工具。除此之外,Visual Studio还提供了众多其他的功能。

与源代码控制系统集成。

图形设计工具。

用于Microsoft Office开发、SharePoint开发和云计算开发的工具。

测试工具。

虽然结合使用您喜欢的文本编辑器以及.NET Framework软件开发包(SDK)提供的命令行工具,也完全可以编写应用程序,但是几乎不这样做。集成编辑和调试工具并结合使用Visual Studio提供的效率改进功能,带来的好处是您可以轻松而高效地编写和调试应用程序。

Microsoft提供了 4个版本的Visual Studio 2010。

学习版。

专业版。

企业版。

旗舰版。

学习版是免费的,用于VB、C#、C++和Web开发。其他3个版本主要用于公司开发环境,提供了额外的功能。

本书的所有示例和屏幕截图都基于采用“基本设置”的Visual C# 2010学习版,如果您使用的其他版本的Visual Studio 2010或采用“专家设置”的Visual C# 2010学习版,屏幕可能有所不同。

启动Visual Studio后,您将看到起始页,如图 1.3所示,它让您能够访问最近使用过的项目、新建项目、了解即将到来的产品发布或阅读最新的开发新闻。

图1.3 Visual C# 2010学习版的起始页

起始页分成了如下几部分。

命令部分:显示命令“新建项目”和“打开项目”。如果您安装的不是Visual Studio学习版,这部分还将显示命令“连接到Team Foundation Server”。

最近使用的项目列表:显示最近使用的项目。单击其中的项目将在Visual Studio中打开它。另外,还可固定项目,如图 1.4 所示,这样打开并关闭其他项目后,该项目将保留在当前位置。

图1.4 固定项目在“最近项目”列表中的位置

选项卡式内容区域:“入门”选项卡包含一个列表,其中列出了帮助主题以及可帮助您学习Visual Studio功能的其他资源;“最新新闻”选项卡列出了选定RSS源的文章。

解决方案、项目和项

Visual Studio使用两种概念帮助您组织源代码以及与之交互:解决方案和项目。

解决方案是一系列源代码文件和相关的元数据,编译时生成一个程序集。解决方案包含一个或多个项目,还有定义项目的额外文件和元数据。每当您新建项目时,Visual Studio都将自动为您创建一个解决方案,必要时可在解决方案中添加其他项目。同一个项目可属于多个解决方案,而复杂的应用程序可能需要多个解决方案。

在解决方案资源管理器中,显示了解决方案、解决方案中的项目以及每个项目中的项,让您能够轻松地查看和管理这些容器以及相关联的项,如图1.5所示。

图1.5 解决方案资源管理器

大多数情况下,您都这样与解决方案资源管理器交互:双击项目中的一个项(如代码文件),在编辑器窗口中显示它;通过上下文菜单添加或删除项或项目。

至此,您应该对 C#语言及其语法足够熟悉,能够编写第一个程序了。如果还没有安装Visual Studio 2010,现在就安装。鉴于这是您创建的第一个程序,因此将继承传统,创建一个在屏幕上显示“Hello, World”的简单程序。

要新建项目,可单击起始页中的“新建项目”命令或工具栏中的“新建项目”按钮,也可使用菜单,图1.6说明了这些命令和菜单所处的位置。

图1.6 “新建项目”命令

这将打开“新建项目”对话框,让您能够指定项目的名称和类型,如图1.7所示。

图1.7 “新建项目”对话框

在这里,您将创建一个名为ConsoleHelloWorld的控制台应用程序。选择项目类型并输入名称后,便可单击“确定”按钮或双击项目类型。这将关闭该对话框、创建项目并显示该项目的默认代码,如图1.8所示。

图1.8 控制台应用程序的默认代码

正如您看到的,Visual Studio为您完成了一些工作,它提供了一些初始代码。添加代码前,先来看看Visual Studio为您生成的代码。

在文件的开头(第1行~第4行)列出了一些命名空间,如图1.9所示。列出每个命名空间时,都使用了编译指令using,这种编译指令由关键字using和命名空间组成。编译指令using告诉编译器和我们引用了一个命名空间,这样就可以使用该命名空间中声明的类型了。

图1.9 编译指令using

紧接着编译指令using,声明了一个名为ConsoleHelloworld的命名空间(第6行)和一个名为Program的类(第8行),如图1.10所示。类和方法将在第3章详细介绍,这里可将类视为一个容器,其边界由左大括号和右大括号定义。

图1.10 Program.cs 的默认内容

现在,将注意力转向我们希望程序执行的操作上。由于这是一个控制台应用程序,您需要与运行该应用程序的控制台窗口交互。通过查看 Framework 类库,您会发现一个名为Console的类,它提供了显示消息和接受键盘输入的方法。

要在控制台窗口中显示文本,需要在定义Main方法的大括号(第11、第12行)之间输入如下语句:

Console.WriteLine("Hello, world");

现在,文件类似于如图1.11所示。

图1.11 添加Console.WriteLine

编写这行代码时,也可使用完整的类型名—System.Console,但是没有必要这样做,因为文件开头有相应的using编译指令。这些using编译指令告诉编译器,您要使用相应命名空间中声明的类型,因此只需指定类型名。然而,如果两个包含的命名空间中有两个同名的类型,那么编译器将无法判断您要使用哪个类型。在这种情况下,仍需要使用完整的类型名。

至此,应用程序“Hello, World”就编写好了。虽然这个示例看起来微不足道,但是实际上并非如此。别忘了,Windows命令提示符并非托管应用程序,因此上述一行代码就封装了所需的全部逻辑:与该非托管应用程序交互并命令它显示指定的文本。在C#编译器看来,这行看似微不足道的代码依赖于数十种类型,这些类型位于十多个命名空间内,它们都是由Framework类库提供的。

注意:保存项目和解决方案

如果您使用的是Visual C# 2010学习版,就将在临时文件夹中创建项目和解决方案。当您关闭Visual C# 2010学习版或首次单击“保存”或“全部保存”按钮时,将被要求指定项目的保存位置。如果您使用的不是 Visual Studio 2010学习版,项目和解决方案将自动保存在您在“新建项目”对话框中指定的位置。

按Ctrl + F5组合键运行应用程序。Visual Studio将保存文件(如果还没有保存),将其编译成一个名为ConsoleHelloWorld.exe的应用程序并运行它。

如果您输入的代码正确,将在一个命令窗口中看到消息“Hello, world”,如图 1.12所示。

图1.12 显示消息“Hel o, world”

消息按任意键继续是Visual Studio添加的,旨在让您能够看到输出。如果没有它,应用程序将在运行并显示消息后退出。

祝贺您成功地编写了第一个C#应用程序。这可能也是您编写的第一个托管应用程序,也可能只是您编写的第一个C#应用程序,但无论是那种情况,都为以后打下了基础。

本章首先介绍了.NET Framework及其组件。就第 1章而言,这些内容可能太深了些,但要成为成熟、成功的.NET程序员,必须对开发.NET Framework的原因及.NET Framework的组件有基本认识。接下来,介绍了C#语言,包括语句、表达式、变量、常量、标识符和关键字。

最后,您使用所学的知识创建了第一个C#应用程序。这可能微不足道—在屏幕上显示文本“Hello, world”能有多大意思,但这只是开始。

在本书中,每章都以之前介绍过的知识为基础,从C#基础知识及其对面向对象编程和面向组件编程的支持开始介绍,逐渐深入,直到诸如多线程和并行编程等高级主题。在此过程中,您将编写各种应用程序,牢固掌握C#和.NET编程知识,为创建更大、更复杂的应用程序打下坚实的基础。

问:.NET Framework是什么?

答:.NET Framework是一个平台,让开发人员能够以独立于语言和平台的方式创建并运行下一代应用程序和Web服务,还有助于消除(起码是减少)众多常见的编程错误。

问:公共语言运行时(CLR)是什么?

答:公共语言运行时(CLR)是.NET Framework的核心,而C#运行在.NET Framework之上。

问:托管应用程序和非托管应用程序之间有何不同?

答:针对.NET Framework编写的代码属于托管代码,其他所有代码都属于非托管代码。

问:垃圾收集是什么?它为何很重要?

答:垃圾收集是.NET Framework提供的一种运行阶段服务,让您无需手动分配和释放内存。这可避免众多常见的编程错误,让您创建的应用程序更正问题,还让您能够将主要精力放在应用程序所需的业务逻辑上。

问:C#是什么?

答:C#是一种面向对象编程语言,它是类型安全的,运行在.NET Framework之上。

问:C#程序会被编译吗?

答:会。在开发阶段,C#程序被编译成通用中间语言(CIL);在运行阶段,即时(JIT)编译器再将CIL编译成可执行的目标代码。

1..NET Framework有哪些组件?

2.为何说通用类型系统很重要?

3.通用中间语言是什么?

4.为何说Framework类库很重要?

5.动态语言运行时向C#提供了什么?

6.下述C#代码合法吗?

7.请解释下述变量声明。

int a, b = 1;

8.下面哪个标识符无效?

A.lightHouse

B._lighthouse

C.22lighthouse

D.lighthouse2

1..NET Framework有 4个组件:公共语言运行时、Framework类库、并行计算平台和动态语言运行时。

2.因为它给每种.NET语言提供了相同的类型描述,并指定了类型的用法,可以集成不同的语言。

3.通用中间语言是一种低级语言,对托管代码进行部分编译时,将生成这种语言的代码。可将通用中间语言视为汇编语言,由表示高级语言代码的低级指令组成。

4.Framework类库提供了大量可重用的类型供所有.NET语言使用,这简化了众多常见的编程任务,从而提高了开发人员的效率。

5 .动态语言运行时让 C#能够以一致的语法使用来自任何地方( COM、IronRuby、IronPython、JavaScript等)的动态对象。

6.这些代码非法,其原因有两个。首先,所有语句都没有以分号(;)结尾;其次,正确的类型名为System.Console.WriteLine,而不是system.console.WriteLine,因为C#区分大小写。

7.像这样同时声明多个变量并赋初值是危险的,因为可能令人迷惑。该语句与下述代码等价:

int a;

int b = 1;

8.C。标识符不能以数字打头。

研究System.Console类的其他功能:修改“Hello, world”应用程序,使其要求用户输入其姓名,然后输出“Hello, name”,其中 name为用户输入的姓名。

本章将介绍:

类型概述;

预定义的C#类型;

运算符;

值类型和引用类型;

可以为null的类型;

类型转换。

第 1章介绍了.NET Framework和C#的基本知识,包括.NET Frame类库、公共语言运行时以及内存自动管理的理念。第1章还简要地介绍了命名空间和类型,探讨了语句、表达式、变量、常量、标识符和关键字,然后创建了一个简单的C#应用程序。

基于您学到的知识,本章首先介绍C#提供的预定义类型以及可对它们执行的运算,然后探讨值类型和引用类型,最后介绍可以为null的类型以及类型转换。

阅读完本章后,您将对C#类型有全面认识,包括值类型、引用类型和可以为null的类型之间的差别。您还将编写一些较复杂的应用程序,它们能够存储和操作简单数据。

C#是一种类型安全的静态语言。这要求您创建任何变量时,都必须将其数据类型告知编译器;编译器将确保您只能将兼容的数据类型存储到变量中。这有助于避免常见的编程错误,让应用程序更稳定、更安全。

类型分三大类:

值类型;

引用类型;

类型参数。

提示:第11章将更详细地介绍类型参数。

提示:指针

实际上还有第4种类型—指针,但核心C#语言不支持。指针包含数据在内存中的实际位置(地址);还可对指针执行算术运算,就像它们是数字一样。虽然指针功能强大,但要正确、安全地使用它们也很难。

然而,有时候可能必须使用指针。所幸的是,几乎只有在非常复杂的情形下才需要指针,因此并非经常需要为此操心。这样的情形包括直接与底层操作系统交互、实现时间非常重要的算法等。

为提供指针的灵活性(这也会带来危险),C#允许您编写不安全的代码,在这些代码中可创建和操作指针。使用不安全的代码和指针时,务必认识到垃圾收集器不会跟踪指针,您必须负责分配和释放内存。从某种意义上说,这类似于在C#程序中编写C语言代码。

除显式的不安全代码块外,C#不允许在其他地方使用指针,这可以完全避免一类常见的错误,让C#更安全得多。

简单地说,值类型是完全独立的,它“按值”复制。这意味着值类型变量包含其数据,不会因为处理一个变量而影响另一个变量。值类型又分为结构、枚举类型和可以为null的类型。

引用类型包含指向实际数据的引用,这意味着两个变量可能指向同一个对象,而操作其中一个变量将影响另一个变量。引用类型又分为类、数组、接口和委托。

注意:统一类型系统

虽然对类型进行了上述分类,但 C#有一个统一类型系统,使得可将任何非指针类型值视为对象。这让值类型获得了引用类型的优点,而不会增加不必要的开销;并可对任何值(包括预定义值类型)调用对象方法。

C#预定义了一组类型,这些类型对应于通用类型系统中的类型。如果您熟悉其他编程语言,就可能会发现这些类型的名称有所不同,但很容易看出它们之间的对应关系。除 object和string外,其他所有预定义类型都是值类型。表2.1列出了预定义类型。

表2.1 预定义的C#类型

续表

通过包括表示布尔值的类型(取值为true或false),可避免混淆布尔变量和整数变量。这有助于消除多种常见的编程错误,让编写含义不言自明的代码更容易。

注意:布尔值

在C语言中,布尔值表示为整数,并让程序员去决定0表示true还是false。通常,C语言程序定义表示整数值0和1的名称常量,以帮助消除这种模糊性,但并没有禁止使用整数表示布尔值。

类型decimal最少包含28个有效位,旨在避免金融计算的误差。double类型主要用于物理计算,以减少表示误差。

object是其他所有引用类型和值类型的基类;string用于表示一系列Unicode字符,这种变量赋值后就不能修改,因此string变量是不可修改的。

除无符号整数类型和sbyte外,其他所有预定义类型都符合CLS,但是使用这些类型时,只要不将其声明为公有的,您的代码就符合CLS。如果这些类型的变量必须是公有的,那么可转而使用相应的符合CLS的类型。

对于sbyte,可使用符合CLS的类型short代替。

对于uint,通常使用符合CLS的类型long代替;如果存储的值小于2147483647.5,那么也可使用int代替。

对于 ulong ,通常可使用符合 CLS 的类型 decimal 代替,如果存储的值小于9223372036854775807.5,那么也可使用long代替。

对于ushort,通常可使用符合CLS的类型int代替,如果存储的值小于32767.5,那么也可使用short代替。

提示:System.Object

所有的值类型以及类、数组和委托等引用类型都是从 object 派生而来的。接口类型可能是从其他接口类型派生而来的,但可转换为object。

类型参数实际上不是从任何类型派生而来的,但可转换为object。

不安全的指针类型既不是从object派生而来的,也不能转换为object,因为它们不受常规C#类型规则的管辖。

这一切意味着C#中的所有非指针类型都可转换为object,但可能并不是从object派生而来的。

C#还有一些特殊的类型,其中最常见的是void。void表示不知道类型。dynamic类型类似于 object,主要的不同之处在于,对这种类型执行的所有操作都将在运行阶段(而不是编译阶段)解析。

虽然void和dynamic都是类型,但var是隐式类型,让编译器根据赋给变量的数据确定变量的类型。

警告:var并非Variant的缩写

最初引入 var类型时,很多人认为它相当于Visual Basic中的Variant类型。Variant 变量可用于存储其他任何数据类型的值,因此不属于强类型;而var类型仍是强类型,因为在编译阶段将用特定数据类型替换它。尽管如此,过度使用var可能降低代码的可读性,因此应慎用。

使用预定义类型

熟悉预定义类型后,下面来看看如何使用它们。请按下面的步骤编写一个应用程序,它创建一些局部变量并显示它们的值。然后,创建一个类型为隐式的变量,并核实实际创建的是一个强类型变量。

(1)新建一个控制台应用程序。

(2)在文件Program.cs的方法Main中,输入如下代码:

(3)按Ctrl + F5组合键运行该应用程序,您将在控制台窗口中看到如图2.1所示的内容。

图2.1 使用预定义类型的应用程序的输出

(4)按任意键关闭控制台窗口并返回到Visual Studio。

(5)在方法Main中,在前面输入的代码后面输入如下代码:

var v = 20;

Console.WriteLine("This is also an {0} value: {1}", v.GetTypeCode(), v);

(6)将鼠标指向关键字var,将出现如图2.2所示的工具提示,这表明v实际上是一个int变量。

图2.2 工具提示表明 v实际上是一个 int变量

(7)按Ctrl + F5组合键再次运行该应用程序,您将看到输出中增加了下面一行:

This is also an Int32 value: 20

(8)按任意键关闭控制台窗口并返回到Visual Studio。

(9)在Main方法中输入如下代码行:

v = "hello";

(10)您将马上看到刚输入的语句下面出现了红色波浪线,还将出现一条错误消息,指出“无法将类型‘string’隐式转换为‘int’”。这是因为编译器已经将一个int值赋给了变量v,而C#的强类型功能禁止您再将string值赋给该变量,因为它是不兼容的类型。

(11)删除第9步输入的代码行,让程序能够通过编译。

2.2.1 其他常用的类型

除标准的预定义类型外,.NET Framework还提供了用于表示其他常用值的类型。不同于预定义类型,这些类型没有C#别名,但对其可执行的操作不受影响。

1.日期和时间

要处理日期和时间值,可使用结构DateTime,它让您能够创建表示日期和时间、仅日期或仅时间的变量。新建DateTime变量时,最常见的方法有两种:使用各种重载的构造函数之一;使用4个静态的分析方法之一—Parse、ParseExact、TryParse或TryParseExact。

结构DateTime有很多属性,表2.2列出了一些最常用的属性。

表2.2 常用的DateTime属性

续表

对日期或时间值进行加减运算时,可使用相应的实例方法,它们返回一个新的DateTime值,而不是修改原来的值。表2.3列出了DateTime中常用的算术运算方法。

表2.3 DateTime中常用的算术运算方法

还可使用减法运算符将两个DateTime值相减,结果为一个TimeSpan实例。TimeSpan实例表示时间间隔,以天数、小时数、分钟数、秒数和毫秒数表示,可正可负。为确保一致性,时间间隔以天数为单位。还可将DateTime与TimeSpan相加或相减,结果为一个新的DateTime实例。

表2.4列出了TimeSpan常用的方法和属性。

表2.4 常用的TimeSpan成员

续表

2.全局唯一标识符(GUID)

GUID是一个128位的整数值,重复的可能性很小,每当需要唯一标识符时就可以使用它。结构System.Guid让您能够创建和比较GUID值,表2.5列出了该结构的常用成员。

表2.5 常用的Guid成员

3.统一资源标识符(URI)

URI是内联网或Internet上可用资源的简洁表示,可以是绝对URI(如网页地址),也可以是相对URI,后者必须根据基本URI进行扩展。

Uri类让您能够新建URI以及访问URI的成员,它还提供了处理URI所需的方法,如分析、比较和合并。这个类的一些常用成员如表2.6所示。

表2.6 常用的Uri成员

Uri实例是不能修改的。要创建可修改的URI,可使用UriBuilder。UriBuilder类让您能够轻松地修改URI的属性,而无需每次修改时都新建实例。表2.7列出了Uri和UriBuilder都有的属性(在Uri中,这些属性是只读的),还列出了只有UriBuilder才有的属性Uri。

表2.7 Uri和UriBuilder都有的属性

程序清单2.1演示了如何使用UriBuilder类。

程序清单2.1 使用UriBuilder

4.BigInteger

类型 System.Numerics.BigInteger 表示任意大的整数值,从理论上说,没有上限和下限。创建BigInteger实例后,可以像使用其他整数类型一样使用,可对其进行基本的数学运算和比较。

结构BigInteger包含其他整数类型的方法和Math类的方法,还有专门针对BigInteger的成员。这个结构的常用成员如表2.8所示。

表2.8 常用的BigInteger成员

续表

程序清单2.2演示了BigInteger类型的一些用法。

程序清单2.2 使用BigInteger

C#支持大量的运算符,但是这里只介绍比较常用的运算符。运算符是一种特殊符号,用于在表达式中指出要执行哪种运算。所有C#预定义类型都支持运算符,但是并非所有类型支持的运算符都相同。

表2.9按优先级顺序列出了所有的C#运算符。在每个类别中,运算符的优先级顺序相同。

表2.9 C#运算符及其优先级顺序

在前面的示例中,您见过赋值运算符(=)多次了。这个运算符将右边的操作数的值存储在左边的操作数表示的变量中。这两个操作数的类型必须相同,否则右边的操作数将隐式地转换为左边操作数的类型。

C#提供了支持标准数学运算的运算符:加法(+)、减法(−)、乘法(*)和除法(/)。与您在学校学到的算术运算规则相比,C #算术运算符的行为稍有不同。具体地说,根据相除的数据类型的不同,除法的行为也稍有不同:将两个整数相除时,结果为整数,并将余数丢弃。要获得整数除法的余数,必须使用求模运算符%。

C#还支持复合赋值运算符,这种运算符将算术运算和赋值合而为一。对于每个标准算术运算符和求模运算符,都有相应的复合赋值运算符,分别是+ =、− =、* =、/ =和% =,这些运算符将赋值分别与加法、减法、乘法、除法和求模合而为一。

例如,假设要将变量加1,可使用标准的算术运算符,其代码通常类似于:

i = i + 1;

通过使用复合赋值运算符,执行这种运算的代码将类似于:

i += 1;

为进一步简化,可使用递增和递减运算符(+ +和--)将变量加 1或减1。递增和递减运算符可位于变量前面(前缀),也可位于变量后面(后缀),它们都将变量加(减)1。前缀与变量组成的表达式的结果为执行运算后的变量值;后缀与变量组成的表达式的结果为执行运算前的变量值。

程序清单 2.3 所示的所有代码行的效果都相同,它演示了如何使用基本运算符、复合赋值运算符和递增运算符。

程序清单2.3 将变量的值加1

i = i + 1;

i += 1;

i + +;

算术运算符示例

为探索赋值运算符、加法运算符、复合赋值运算符以及递增和递减运算符的行为,请执行下述步骤。对于递增和递减运算符,还将探索其前缀形式和后缀形式在行为方面的差别。

(1)新建一个控制台应用程序。

(2)在文件Program.cs的Main方法中,输入如下代码:

(3)按Ctrl + F5组合键运行应用程序,您将在控制台窗口中看到如图 2.3所示的内容。

图2.3 使用算术运算符的应用程序的输出

(4)按任意键关闭控制台窗口并返回到Visual Studio。

如表2.10所示,关系运算符用于比较两个值,结果为布尔值。

表2.10 C#关系运算符

在很多编程语言中,赋值和相等运算符为同一个符号,很容易混淆。这可能导致意外赋值,是当今常见的编程错误之一。为避免混淆,C#将 = =用作相等运算符。

如表2.11所示,逻辑运算符用于布尔表达式中,结果为true或false。

表2.11 C#逻辑运算符

续表

逻辑运算符的行为规则很容易总结。假设x和y都是布尔表达式,表2.12总结了逻辑运算的结果。

表2.12 逻辑运算符真值表

关系运算符和逻辑运算符示例

请执行如下步骤,以核实表2.10和表2.11的内容。

(1)新建一个控制台应用程序。

(2)在Main方法中,声明两个整型变量x和y,并将它们分别初始化为20和10。

(3)基于表2.10和表2.11的表达式,使用如下格式编写一系列Console.WriteLine语句,并用这些表中的表达式替换其中的expression:

Console.WriteLine("expression: {0}", expression);

(4)按Ctrl + F5组合键运行应用程序,并检查结果是否与这些表格的“结果”栏中的内容匹配。

注意:简化评估

在 C#中,添加运算符执行简化评估,即仅当第一个表达式不会导致整个表达式的结果为false时,才评估其他表达式。然而,逻辑运算符简化评估。

执行简化评估时,如果AND运算符连接的第一个表达式为false,就没有必要再评估其他表达式,因为整个表达式的结果必然是 false。同样,如果OR运算符连接的第一个表达式为true,则没有必要再评估其他表达式,因为整个表达式的结果必然为true。仅当第一个表达式不足以确定整个表达式的结果时,才会进一步评估其他表达式。

条件运算符(也称为三目运算符,因为它涉及3项)对编写简洁表达式很有帮助,它对条件进行评估,并根据结果返回两个值之一。

条件运算符的格式如下:

condition ? consequence : alternative

当condition为true时,将计算consequence并返回结果;如果condition为false,则计算alternative并返回结果。

警告:三目运算符常见的问题

这个运算符是右结合的,这与其他大部分运算符(左结合的)不同。这意味着对于下面的表达式相当于 a ? b : ( c ? d : e ):

a ? b : c ? d : e

条件表达式的类型是由consequence和alternative的类型决定的,而不是它被赋给的变量决定的。

因此,consequence 和 alternative 的类型必须相同,这意味着下面这样的表达式无法通过编译,因为其consequence的类型为int,而alternative的类型为string:

object x = b ? 0 : "hello";

虽然上述代码不实用,根本不应在其他地方使用,但是其正确的书写方式可能如下:

object x = b ? (object)0 : (object)"hello";

本书前面说过,C#不允许您使用未初始化的变量,这意味着使用变量前,它必须有值。虽然这种“必须赋值”的理念有助于减少错误(因为编译器强制执行这种理念),但是如果对于每个字段,都必须显式地提供默认值,就将非常繁琐。

为减轻这种负担,给字段(成员变量)指定了合适的默认值。表2.13列出了各种预定义数据类型的默认值。

表2.13 默认值

正如您看到的,对于整型类型,默认值为零。char类型的默认值为空字符,而bool类型的默认值为false。类型object和string的默认值为null,即没有指向任何对象。

这些默认值意味着值类型不能为 null,看起来好像是合理的。然而,当您使用数据库、其他外部数据源或其他可能没有指定值的数据类型时,会带来一定的限制。一个典型的示例就是数据库中的数值字段,它可以存储任何整型数据,也可能没有值。

对于这种问题,可以为null的数据类型提供解决方案。可以为null的类型是这样一种值类型:可表示其底层类型指定范围内的值以及null值。可以为null的类型用语法Nullable<T>或T?表示,其中T是一种值类型。语法T?使用更广泛。给可以为null的类型的变量赋值时,方法与给其他变量赋值相同:

int = 10;

int? = 10;

int? = null;

要获取可以为 null 的类型的变量的值,应使用方法 GetValueOrDefault,它返回赋给变量的值,如果没有赋值,就返回底层类型的默认值。另外,还可以使用属性 HasValue (如果给变量赋值了,该属性将为true)和Value(它返回变量的实际值,如果值为null,就将引发异常)。

所有可以为 null 的类型(包括引用类型)都支持 null 合并运算符(??)。将可以为 null的类型的变量赋给不能为null的类型的变量时,可使用该运算符指定要返回的默认值。如果该运算符左边的操作数为 null,就返回右边的操作数;否则,返回左边的操作数。程序清单2.4演示了如何使用null合并运算符。

程序清单2.4 null合并运算符

使用可以为null的类型

为研究如何使用可以为 null 的类型,请执行下述步骤。您将创建一个可以为 null 的 int变量,使用HasValue、Value和GetValueOrDefault(),并在可以为null的int变量和不能为null的int变量之间进行隐式转换。

(1)新建一个控制台应用程序。

(2)在Main方法中,声明一个名为x的int变量,并将其初始化为10。然后声明一个可以为null的int变量nx,并将其初始化为null。

(3)输入下述语句:

Console.WriteLine("nx has a value? {0}", nx.HasValue);

Console.WriteLine("x == nx: {0}", x == nx);

Console.WriteLine("x != nx: {0}", x != nx);

(4)现在将nx设置为20,并输入如下语句:

(5)将nx设置为null并输入如下语句:

Console.WriteLine("nx = {0}", nx ?? -1);

Console.WriteLine("nx = {0}", nx.GetValueOrDefault());

Console.WriteLine("nx = {0}", nx.GetValueOrDefault(-2));

(6)最后,将nx设置为10并输入如下语句:

Console.WriteLine("nx = {0}", nx ?? -1);

Console.WriteLine("nx = {0}", nx.GetValueOrDefault());

Console.WriteLine("nx = {0}", nx.GetValueOrDefault(-2));

(7)按Ctrl + F5组合键运行应用程序并观察输出,如图 2.4所示。

图2.4 使用可以为nul 的类型的应用程序的输出

(8)按任意键关闭控制台窗口并返回到Visual Studio。

了解值类型和引用类型后,如果希望值类型的行为类似于引用类型,该如何办呢?

前面说过,作为统一类型系统的一部分,所有值类型都可以转换为 object。值类型变量需要用作引用类型时,将自动创建一个对象“箱”,并将值复制到箱子中。装箱后,对一个变量的操作不会影响另一个。将对象箱变回到原来的值类型时,将把箱子内的值复制到变量中。

注意:装箱和取消装箱

值类型和引用类型之间的转换通常称为强制转换(cast),因为这种转换使用C# cast运算符,但是相应的CIL指令为 box和 unbox。

装箱转换总是隐式的,它将值类型转换为引用类型。取消装箱总是显式的,它将装箱的值类型(引用类型)转换为值类型。

装箱和取消装箱操作占用的资源很多,开销也很大,应尽可能避免并确保使用正确的类型来解决问题。

表 2.14 列出了各种预定义类型支持且能成功完成的隐式转换。之所以允许这些隐式转换,是因为从原始数值类型转换为新的数值类型时不会降低量级。

提示:隐式转换

隐式转换可能降低精度,但是不应降低量级。以将 int 值转换为 float值为例,它们都是32位的,但是并非每个int值都可以精确地表示为float,这将导致精度降低。然而,由于float的取值范围比int大,因此这种转换不会降低量级。

表2.14 预定义类型的隐式转换

在转换可能降低精度时,必须进行显式转换,此时需要指定要将原始值转换为哪种类型。显式转换的形式为(T) E,如图 2.5所示,它将E的值转换为类型T。

图2.5 剖析显式转换

显式转换存在的问题是,如果您不小心,代码就可能能够编译,但是运行时会失败。显式转换告诉编译器,您确定这种转换能够成功,如果不成功,导致的运行阶段错误也是可以接受的。

为降低显式转换在运行阶段失败的可能性,C#提供了 as运算符,其形式为 e as T,其中e是一个表达式,而T必须是引用类型或可以为null的类型。as运算符告诉编译器,有充分的理由相信转换将成功,它试图将值转换为类型T并返回结果,如果转换失败,就返回null。

为利用as运算符,可将图2.5所示的代码重写为如下形式:

int? i = 36;

object boxed = i;

int? j = boxed as int?;

转换示例

为探索如何使用转换,请按如下步骤将值类型转换为引用类型。这个应用程序演示了对值类型变量、引用类型变量和装箱的值类型变量执行操作时,将如何影响其他变量。

(1)新建一个控制台应用程序。

(2)在Main方法中,声明一个名为i的int变量,并将其初始化为36。然后声明一个名为boxed的object变量,并使用变量i初始化它。

(3)输入两条Console.WriteLine语句,以显示变量i和boxed的值。

(4)使用显式转换将boxed的值加2。

(5)复制并粘贴第3步输入的两条Console.WriteLine语句,以核实boxed的值发生了变化,但i的值没变。

(6)将i的值加1,复制并粘贴第3步输入的两条Console.WriteLine语句,以核实i的值发生了变化,但boxed的值没变。

(7)使用显式转换将boxed的值赋给变量i。

(8)最后,声明两个可以为null的int变量(h和j),将h初始化为null,将j初始化为i的值;再声明一个名为jboxed的object变量,并将其初始化为j。

(9)输入下述代码:

Console.WriteLine("h has a value? {0}", h.HasValue);

h = jboxed as int?;

Console.WriteLine("h now has the value {0}", h.Value);

(10)按Ctrl + F5组合键运行应用程序,其输出如图 2.6所示。

图2.6 装箱、取消装箱和显式转换示例的输出

(11)按任意键关闭控制台窗口并返回到Visual Studio。

本章继续介绍C#基本知识,首先探讨了C#提供的预定义类型以及可对其执行的众多操作,然后介绍了值类型和引用类型,包括如何将值类型用作引用类型以及如何创建可以为null的类型的变量。

为探索这些概念,您编写了几个简单的C#应用程序。这些应用程序也许没什么吸引力,但是它们有助于打下坚实的基础,让您能够创建更复杂的应用程序。随着您的C#基础日益牢固,示例和练习也将越来越复杂。

问:静态类型意味着什么?

答:C#是一种静态类型语言,这要求您创建任何变量时,都必须将其数据类型告知编译器;而编译器将确保您只能将兼容的数据类型存储到变量中。

问:C#支持指针吗?

答:C#确实支持指针,但是指针并非其核心部分,您只能在不安全代码中使用指针。

问:在C#中,统一类型系统为何重要?

答:通过提供统一类型系统,C#让您能够将任何值类型作为 object,而不会带来不必要的开销。

问:所有预定义类型都符合CLS吗?

答:不,无符号整数类型和sbyte不符合CLS,但是必要时可使用符合CLS的类型替换它们。

问:使用var声明的变量是强类型的吗?

答:是的。使用 var 声明的变量仍是强类型的,因为编译器将在编译阶段指定这种变量的类型。var并不相当于Visual Basic类型中的Variant。

问:值类型和引用类型之间有何不同?

答:值类型变量直接存储数据,引用类型变量存储的是指向数据的引用。

问:值类型可以为null吗?

答:值类型分为可以为null的和不可以为null的。对于可以为null的值类型,取值范围为其底层类型的取值范围加上null;不可以为null的值类型不能为null。

问:为何应尽可能避免装箱和取消装箱操作?

答:因为它们将耗用大量资源,带来很大的开销。

1.C#类型分哪三大类?

2.对金融计算来说,哪种预定义类型很有用?为什么?

3.哪种类型是所有预定义类型的基类?

4.为何单独提供bool类型很重要?

5.所有字符串和字符数据都以Unicode方式存储吗?

6.字符串不可修改意味着什么?

7.前缀递增运算和后缀递增运算之间有何不同?

8.null合并运算符(??)可用于引用类型和可以为null的值类型吗?

9.请描述装箱过程。

10.long可隐式地转换为int吗?

1.C#类型分引用类型、值类型和类型参数。

2.decimal类型对金融计算来说很有用,它避免了其他浮点类型常见的表示误差。

3.归根结底,C#中的所有预定义类型及其他一切都是从object类型派生而来的。

4.通过包括单独的bool类型,消除了整数0和1带来的二义性,有助于避免多种常见的编程错误。

5.是的,在C#中,所有字符串和字符都存储为Unicode,这有助于本地化。

6.由于字符串是不可变的,因此赋值后就不能修改。这意味着每当执行字符串拼接操作时,都需要创建新的 string 对象来存储结果。如果在短时间内重复执行大量这样的操作,就可能导致内存占用率急剧上升,因此应考虑转而使用StringBuilder类。

7.在前缀递增运算中,结果为递增之后的变量值;在后缀递增运算中,结果为递增之前的变量值。

8.是的,null合并运算符可用于可包含null的任何类型,包括对象。

9.将值类型用作引用类型时,将发生装箱操作,这需要新建一个实例来存储装箱后的值。对装箱得到的对象执行操作时,不会影响原来的值。

10.不能将long隐式地转换为int,因为这将降低量级;但是可以显式地进行转换。

1.请编写一个控制台应用程序,生成表2.12所示的真值表。

2.请编写一个控制台应用程序,演示值类型和引用类型之间的差别。在该应用程序中,应声明两个int变量和两个类型为LightHouse的对象变量。为创建LightHouse类,新建一个名为LightHouse.cs的类文件,并将为这个类自动生成的代码替换为如下代码:

图书在版编目(CIP)数据

Visual C# 2010入门经典/(美)道曼(Dorman,S.)著;张劼译.--北京:人民邮电出版社,2011.4

ISBN 978-7-115-25161-9

Ⅰ.①V… Ⅱ.①道…②张… Ⅲ.①C语言—程序设计  Ⅳ.①TP312

中国版本图书馆CIP数据核字(2011)第053117号

版权声明

Scott Dorman: Sams Teach Yourself Visual C# 2010 in 24 Hours

ISBN: 0672331015

Copyright © 2010 by Sams Publishing.

Authorized translation from the English languages edition published by Sams.

All rights reserved.

本书中文简体字版由美国Sams出版公司授权人民邮电出版社出版。未经出版者书面许可,对本书任何部分不得以任何方式复制或抄袭。

版权所有,侵权必究。

Visual C# 2010入门经典

♦著 [美] Scott Dorman

译 张劼

责任编辑 傅道坤

♦人民邮电出版社出版发行  北京市崇文区夕照寺街14号

邮编 100061  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

北京  印刷

♦ 开本:787×1092 1/16

印张:22

字数:550千字  2011年5月第1版

印数:1-000册  2011年5月北京第1次印刷

著作权合同登记号 图字:01-2010-3633号

ISBN 978-7-115-25161-9

定价: 元

读者服务热线:(010)67132705 印装质量热线:(010)67129223

反盗版热线:(010)67171154

广告经营许可证:京崇工商广字第0021号

相关图书

程序员的制胜技
程序员的制胜技
C#开发案例精粹
C#开发案例精粹
C#完全自学教程
C#完全自学教程
C#从入门到精通(第2版)
C#从入门到精通(第2版)
 C#初学者指南
C#初学者指南
C#本质论(第4版)
C#本质论(第4版)

相关文章

相关课程