博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《C#高级编程第三版》第一部分第一章初稿发布!!!
阅读量:2455 次
发布时间:2019-05-10

本文共 25248 字,大约阅读时间需要 84 分钟。

 

 

第一部分:C#语言

 

第一章     .NET架构

 

 

翻译:coofucoo

 

 

 

 

 

 

 

 

 

 

 

第一章        .NET架构

      阅读本书你会发现,本书通篇都强调,C#语言决不该被孤立的看待,它必须与.NET Framework一起考虑。C#编译器是以.NET为特定目标的,所以这就意味着用C#写出来的代码必须运行在.NET Framework.的支持上。这样,对C#就有两个重要的推论:

       C#的体系结构和方法论反映了.NET的根本方法论。

    在许多情况下,C#的许多专用语言特性依赖于.NET的特性或者.NET基础类。

    由于这种依赖关系,在开始C#编程之前,理解.NET的体系结构和方法论是重要的。这也正是本章的目的。

    我们首先会了解一下当所有以.NET为目标架构的语言(包括C#)被编译和运行时都发生了什么。一旦我们有了大致的了解,我们将更加详细地了解一下Microsoft Intermediate Language (MSIL 或者简称 IL).NET上面所有的语言最终都会被编译为这种汇编语言。特别的,我们将了解一下ILCommon Type System (CTS)Common Language Specification (CLS)是怎样共同作用使得.NET下面的语言可以协同工作的。我们也要讨论各种语言(Visual Basic C++)怎样适合.NET的。

       了解完这些后,我们将要继续了解一下.NET的其他特性,包括程序集、命名空间和.NET基础类。最后结束这一章之前我们要像C#开发人员一样对可以创建的应用程序的种类做一个大致的了解。

1.1      C#.NET的关系

相比较其他语言来说,C#是一种新的编程语言,并且以下两个方面体现了他的重要性:

       它是专门为微软.NET框架(一个为了开发、部署和执行分布式应用软件而设计的功能丰富的平台)设计的。

    它是一种基于现代面向对象设计方法的语言,在他设计时,面向对象原则已经得到显著应用20年之久,而且微软吸收了所有这些面向对象语言的经验。

    一个重要的问题是要弄明白C#本身就是一种语言。尽管它是设计来产生.NET环境下的代码的,但是它不是.NET的一部分。还有一些特性.NET支持,但是C#并不支持,而且可能更令你惊奇的是,还有一些C#语言的特性.NET竟然不支持!(例如,一些运算符重载)

    但是,因为C#语言是用于.NET的,所以对我们来说如果我们想要用C#开发出高效的软件,了解.NET Framework就是重要的。因此,在这一章,我们将要花些时间迅速透过表层来观察一下.NET。好,我们开始吧!

1.2      公共语言运行时

    .NET Framework的核心是运行时执行环境,其被大家称为公共语言运行时(CLR)或者.NET运行时。在CLR的控制下运行的代码常常本称作托管代码。

    但是,在被CLR执行之前,所有我们开发的源代码(用C#或者其他的语言)都需要被编译。这样的编译需要两个步骤:

    1. 将源代码编译为中间语言(IL代码

    2. CLR将中间语言代码编译为特定平台上面的代码

    这两步编译过程是非常重要的,因为中间语言(IL的存在正是.NET众多优点的关键。好,让我们来看看为什么。

1.2.1        托管代码的优点

   Microsoft中间语言与Java字节码共享一种思路,他们都是一种语法简单的低级语言(建立在数字代码基础上,而不是文本代码),可以被快速的翻译为本地机器码。代码有这样设计良好并且通用的语法是意义重大的优点。

1.2.1.1              平台无关性

    首先,这就意味着包含字节代码指令的相同的文件可以被部署在任何的平台,在运行时编译过程的最后一个阶段可以很容易的完成,所以代码可以运行在特定的平台上。换句话说,源代码被编译成中间语言使得我们可以获得.NET的平台无关性,这和在Java平台上源代码被编译为Java字节码以获得平台无关性的道理是一样的。

    你应该注意到.NET这种平台无关性目前还是理论上的,因为在本书写作的时候,还只有在Windows平台上才完全实现了.NET。但是,已经有一个部分实现的.NET(参见Mono项目,一个正在致力于建立于开源平台之上的.NET,具体请访问

1.2.1.2              性能改进

    尽管前面和Java作了比较,但是实际上中间语言(IL)比Java字节码更有野心。IL总是即时编译(被称为JIT编译),然而Java字节码常常是解释性的。像Java那样解释编译的一个缺点是,在运行时,将Java字节码翻译为本地可执行的代码这个过程导致了性能的损失(这里不包括最近新加入的Java平台,有些特定的Java平台已经实现了JIT编译)。

    并不是一次就将整个应用程序编译(那样的话导致应用程序在启动的时候变得很慢),JIT仅仅是简单的将需要用到的一部分代码编译(就像他的名字,即时编译)。当代码一旦被编译完成后,这些代码就会被作为本地可执行的结果保存下来直到应用程序退出。所以,当这一部分代码再次被调用的时候不需要再次编译。Microsoft认为这个过程的效率比一开始就编译整个应用程序的代码要高得多。因为实际上每次运行大多数部分的应用程序代码都被执行的可能性是不大的。采用这种JIT编译器,这样的代码将永远不会被编译。

       这就解释了我们为什么认为托管IL代码执行起来就像是执行本机代码一样快。但是,这并没有说明为什么Microsoft认为我们将会获得性能的提高。性能获得提高的一个原因是,当最后一个编译阶段在运行时发生时,JIT编译器已经确切的知道了程序将要运行于的处理器类型。这就意味着编译器可以优化最后的可执行代码以便利用特定处理器所带来的任何特性和特定的机器代码指令。

       传统的编译器也会优化代码,但是它仅仅是完成了与程序将要运行于的特定的处理器无关的优化。这是因为传统的编译器是在软件装载之前将源代码编译为本地执行代码的。这就意味着在编译之时编译器并不知道软件将要运行于的处理器类型,例如究竟是会运行在x86兼容处理器上还是Alpha处理器上。比如,Visual Studio 6就是针对普通的奔腾处理器作了优化,所以这也意味着它所产生的代码无法利用Pentium III处理器的硬件特性。相反,JIT除了可以完成Visual Studio 6所完成的优化工作之外,还可以针对软件将要运行于的特定处理器做优化。

1.2.1.3    语言互操作性

    IL的作用不仅仅是赋予了平台无关性,它还促进了语言互操作性。简单的说,你可以将任何一种语言的代码编译为IL,而且这个编译好了的代码可以和任何其他的语言编译成的IL进行交互。

    现在你或许想要知道除了C#之外还有什么语言可以跟.NET进行交互,是吗?好,那么就让我们简要的讨论一下究竟其他的语言是怎么使自己适合.NET.的。

Visual Basic .NET

Visual Basic 6升级到最新的Visual Basic.NETVisual Basic经历了一番彻底的改造。从最近几年Visual Basic 6的发展情况来看,它并不是一个适合运行于.NET的语言。比如,他过度的与COM集成,并且仅仅是将源代码通过事件处理的程序显示给开发人员,这样就导致源代码中的大量后置代码开发人员并不能利用。还不仅仅是如此,Visual Basic 6还不支持实现继承,而且Visual Basic 6的标准数据类型也和.NET的不兼容。

       现在,Visual Basic 6已经升级成了新的Visual Basic .NET,而且语言的变化如此知道所以你最好将Visual Basic .NET看作是一种新的编程语言。已经存在的Visual Basic 6代码现在已经不能被当作Visual Basic .NET的代码编译。如果想要将以前的Visual Basic 6代码转移到Visual Basic .NET就必须做出巨大的修改。但是Visual Studio .NET(新一代的基于.NET的VS)可以替你做这些修改中的大部分工作。如果你试图在Visual Studio .NET打开原来Visual Basic 6的工程,Visual Studio .NET就会为你升级工程,这意味着它将用Visual Basic .NE重写原来的Visual Basic 6源代码。尽管这样升级代码的繁重工作已经大大的减轻,但是你还是学要检查新产生的Visual Basic .NET代码已确定它确实是按照预期正确工作的,毕竟,这样的转换可能并不完美。

         升级后的另一个改变是Visual Basic .NET不会再被编译为本地执行代码了,相反和C#一样,他被编译为IL。也许你还是需要继续编写Visual Basic 6代码,也许你确实需要这样,你就必须在原来的Visual Studio 6进行开发,而且这样的代码也会完全忽略.NET Framework

Visual C++ .NET

         为了适应Windows,微软Visual C++ 6已经对其作了大量特定的扩展。到了Visual C++ .NET,其又被扩展以支持.NET Framework。这意味着现有的C++源代码可以不用修改的继续编译为本地可执行代码。但是,这也说明这样的本地代码和.NET运行时是没有什么关系的。如果你想要使你的C++代码运行在.NET Framework之上,你就需要在你的代码的开头简单的添加下面这一行:

  #using <mscorlib.dll>                      

       你也可以传递一个“/clr参数给编译器,这样可以设置你想编译为托管代码,编译器就会将源代码编译为IL而不是本地机器码。有趣的是在将C++编译为托管代码时,编译器会编译出一种包含内嵌本地执行码的IL。这样也就是说你可以在你的C++代码里面混用托管类型和非托管类型。像这样的托管C++代码:

       class MyClass

{

  

       定义了一个普通的C++类,而下面的代码:

       __gc class MyClass

{

     

       将要定义一个托管的类,就像你在C#或者Visual Basic .NET定义的类一样。托管C++代码比C#代码更好的一点是你没必要采用COM交互功能就可以直接调用非托管C++类。

         如果你试图用在托管类型上使用一个.NET并不支持的功能编译器就将报告错误(例如,模板或者多继承)。当然你也会发现当你使用托管类时你需要使用非标准C++特性(例如上面提到的关键字“__gc”)。

       由于C++的自由,C++允许低级指针操作等方面的特性,所以C++编译器不能生成能够通过CLR的内存类型安全测试的代码。如果通过CLR的内存类型安全测试对你的代码很重要,那么你就用其他的语言来编写你的源代码(例如C#或者Visual Basic .NET)。

Visual J# .NET

       最新的一个加入这个大家庭的语言是Visual J# .NET。在.NET Framework 1.1推出之前,用户如果想要使用J#的话就必须另外再去下载。但是现在,J#已经内建于.NET Framework之中。真因为如此,J#用户现在可以Visual Studio .NET所有通用的优点了。Microsoft希望大多数J++用户可以看到,如果他们想要在.NET下编写程序的话,J#是最方便的选择。与那些以Java运行时刻库为目标运行环境的语言不同,J#.NET下面的其他语言一样运用.NET的基础类库。这就意味着你可以用J#来建立ASP.NET Web应用程序、Windows FormsXMLWeb 服务,还有所有像C# Visual Basic .NET能够做的。

脚本语言(Scripting languages

       尽管由于.NET的降临脚本语言的重要性好像有所削弱,但是它们现在仍然还在使用。另一方面,Jscript现在已经被升级为JScript .NET。我们现在可以用JScript .NET来编写ASP.NET程序,像一个编译程序一样运行JScript .NET,而不是以前的解释性程序,并且编写大量的JScript .NET代码。有了ASP.NET,已经没有必要在服务期端Web页面上面使用脚本语言。但是,现在VBA还是用来编写Microsoft Office Visual Studio的宏程序。

COM COM+

       从技术上讲,COM COM+并不是面向.NET的技术,因为基于这两种技术的组件并不能被编译为IL(虽然从某种程度上面将是有可能的,如果原来的COM组件是用C++编写的,那么就可以利用托管C++来实现)。但是,因为COM+的有些特性并没有被.NET支持,所以它仍然是一个重要的工具。同样,COM组件也同样仍然能够,.NET合并了COM的互操作功能使得托管代码可以调用COM组件,反之也可以。(第29章将要讨论这点)但是,一般而言对于大多数用途来说使用.NET组件来说是非常方便的,这样的话你可以利用.NET基础类以及其他的托管代码的优点了。

1.3      近看中间语言

从上面的章节我们可以看到,Microsoft中间语言显然在.NET Framework中扮演了一个非常重要的角色。作为一个C#开发者,我们现在已经了解了C#代码在执行之前会被编译成IL(确实是这样,C#仅仅被编译为托管代码)。这样,我们现在再近距离观察IL的主要特性就变得很有意义,因为任何面向.NET的语言也需要支持这些IL的主要特性。

下面就是这些IL的重要特性:

面向对象和接口的支持

值类型和引用类型差别巨大

强数据类型

使用异常来处理错误

属性的应用

让我们现在就开始近距离观产以下这每一个特性吧。

1.3.1  面向对象和接口的支持

.NET的语言独立性有一定的实际限制。IL不可避免的要实现一些特定的程序设计方法论,这就意味着如果有种语言最终要编译为IL的话,那么这种语言本身也要与这种方法论保持一致。所以对于IL来说,Microsoft选择的是只支持单继承的经典的面向对象方法论。

       那些对面向对象理论还不了解的读者应该参考以下附录A已获得更多的信息。在 可以获得附录A

    除了经典的面向对象方法论,IL还引入了接口的概念,这种接口的概念在Windows上面是COM第一次实现的。.NET的接口和COM的接口并不一样,.NET的接口并不需要支持任何COM基础结构(例如,他们并不是派生于IUnknown也不用GUIDs相关联)。但是.NET共享了COM的接口思想,接口提供一个契约,类如果要实现一个给定的接口的时候必须提供这个接口的方法和属性的实现办法。

1.3.1.1    面向对象和语言互操作性

现在我们已经看到,如果要在.NET下面工作和编程就必须遵守经典面向对象方法论,所编写出来的代码也必须编译为IL。但仅仅这些并不足以提供语言互操作性。毕竟,C++ Java也都遵守同样的面向对象规范,但是他们却仍然不能被认为是可以互操作的。我们需要更进一步的了解语言互操作性的概念。

首先,我们需要考虑一下我们所说的语言互操作性的概念。毕竟,COM允许用不同语言编写的组件可以通过调用彼此的方法在一起合作。那这样的话还有什么不足么?COM,通过二进制的标准,允许组件可以实例化其他的组件并且调用它们的方法和属性,而不用考虑这些组件都是用什么语言编写的。但是,为了达到这样的目的,每个对象都必须通过COM运行时来实例化,而且需要通过接口来访问。依据关系组件的线程模型,整理不同县城之间的内存空间或者运行组件或者两者都有的数据将会带来很大的性能损失。在极短的情况下,组件驻留在可执行文件中,而不是DLL文件,这样就需要分别创建不同的过程来运行它们。重要的是组件可能需要互相沟通,但是只能通过COM运行时才能沟通。在COM下,用不同的语言编写的组件无法直接相互沟通,或者互相实例化,这些操作只是利用COM作为媒介。还不仅仅是这些,COM的体系结构不允许实现继承,这样COM就丧失了许多面向对象编程的优点。

另一个相关的问题是,当调试的时候,你仍然需要单独调试这些由不同语言编写的组件。在调试器上单步调试多种语言是不可能的。所以我们所说的语言互操作性的真正含义是由一种语言编写的类可以直接与另一种语言编写的类直接通信。特别的:

       用一种语言编写的类可以继承由另一个语言编写的类

       一个类可以包含另一个类的实例,而并不在意两个类的语言是什么

       一种语言的对象可以直接调用另一种语言的对象

       对象(或者对象的引用)能够在方法之间传递

       当在不同语言之间调用方法时我们可以单步调试,即便当这样意味着在不同语言的源代码中进行单步调试。

       这是一个绝对雄心勃勃的计划,但是令人惊奇的是,.NET IL达到了这个目标。在调试器中进行单步调试方法之间的调用时,不是CLR,而是Visual Studio .NET IDE提供给我们这种方便的特性。

1.3.2        值类型和引用类型

和其他的任何一种编程语言一样,IL提供了许多预先定义好的原始数据类型。但是,明显区别值类型和应用类型也是IL的一个重要特性。值类型就是那些直接存储数据值的那些类型,而引用类型只是存储数据值所在的内存单元的地址,通过这个地址,我们就可以找到相应的数据。

       C++的术语中,与引用类型相似的是指针,而在Visual Basic中,对象与这里的引用类型最相似,在Visual Basic中对象总是通过引用来访问的。IL同样因为存储引用类型作了详细的规范,它们总是被存储在一块叫做托管堆的内存里,相反值类型的变量通常是存储在堆栈中的(尽管在一个引用类型中定义了一个值类型,但它们仍然被内联存储在堆中)。我们将要在第三章讨论堆栈和堆,以及他们是怎么工作的。

1.3.3        强数据类型

IL的一个重要特性是它是基于强数据类型异常检查的。这就是说在IL中所有的变量都必须被清楚的表明变量特定的数据类型(例如,像在Visual Basic 脚本语言中允许的Variant数据类型IL是不存在的)。特别的,IL不允许引起歧义性数据产生的操作。

举个例子来说,因为Visual Basic 6可以自动的进行类型转换,所以Visual Basic 6开发者以前可以传递变量而不用过多的考虑他们的类型。C++开发者以前习惯于在不同类型之间转换指针的类型。执行这样的操作可以极大地提高性能,但是这样也破坏了类型的安全性。因此,这种情况仅仅是在某些需要编译为托管代码的语言的特定情况下才被允许。确实是这样,指针(相对于引用)只在被标记了的C#代码块中才被允许,而在Visual Basic中根本不能使用指针(尽管在托管C++中允许使用)。在代码中使用指针会导致在CLR执行的类型安全性检查中失败。

你应该注意到仍然有一些.NET依然允许在类型检查上面放松要求,比如Visual Basic .NET,但也只是编译器确定这样的类型能够保证后台IL的类型安全性。

尽管强制类型安全性检查也许会带来性能的损失,但是在许多情况下,.NET所提供的各种服务所带来的好处远远超过了类型安全性检查所带来的性能损失。这些服务包括:

语言互操作性

垃圾收集器

安全性

应用程序域

下面就让我们赖以其具体了解一下为什么强数据类型.NET的这些特性特别重要。

1.3.3.1  对语言互操作性来讲强数据类型的重要性

如果一个类是有其他的类派生而来,或者是想要包含其他的类的实例,他就需要知道其他的类所用到的数据类型。这样正是为什么强数据类型这么重要的原因。确实,过去正是没有这数据类型信息方面的详细规范才总是阻碍语言之间相互继承以及实现互操作性。这类信息不是简单的出现在标准的可执行文件或者DLL文件里。

       假设一个Visual Basic .NET的类的一个方法定义了返回值是一个“整型“——Visual Basic .NET的一种标准数据类型。但是C#却没有这样名称的一个数据类型。显然,如果编译器知道如何将Visual Basic .NET的整型映射为C#已经定义的某种类型的话,我们就可以在Visual Basic .NET获得从C#的类里继承,调用方法,使用返回值的能力。

通用类型体系

       这个数据类型方面的问题.NET是通过Common Type System (通用类型体系,简称CTS)解决的。CTS定义了一些在IL中可以使用的预先定义数据类型,这样所有希望以.NET Framework为运行平台的语言最终都需要生成建立在这些类型上面的代码。

       在考虑一下我们刚刚举的那个例子,Visual Basic .NET的整型数据类型实际上是一种32位带符号整数,这种数据类型与IL中的数据类型Int32完全吻合因此这就是IL代码的数据类型明细。因为C#也知道这种数据类型,所以现在前面的问题已经解决了。在源代码中,C#Int32所对应的关键字是int所以只需要简单的做一下转换,将Visual Basic .NET方法的返回类型转换为int

       CTS不仅仅是定义了基本数据类型,他还定义了一个内容丰富的类型层次结构,这种层次结构包含了定义好的要点,其他的语言可以在此基础上定义自己的类型。Common Type System的层次结构反映了IL的单继承面向对象方法论。如图1-1所示:

1-1

       下面的表格用来说明上图中所提到的类型。

    

   

类型

基类,任何类型都继承于他

值类型

基类,任何值类型都继承于他

引用类型

通过引用来访问,并且存储在堆中的数据类型

内置值类型

包括大多数标准基本类型,用来表示数字、布尔类型、字符

枚举类型

枚举值的集合

用户定义值类型

在源代码种定义并且保存为值类型的数据类型。C#语言里,所有的结构体都是这种

接口类型

接口

指针类型

指针

自我描述类型

为垃圾收集器提供自身信息,这样就能利用垃圾收集器带来的好处,这样的类型就是自我描述类型(参看下一节)

数组

包含一批对象的数据类型

类类型

除了数组之外的自我描述类型

委托

设计用来为方法保存应用的类型

用户定义引用类型

在源代码中定义并且保存为引用类型的数据类型。C#语言里,所有的类都是这种

装箱的值类型

将临时包装值类型在一个引用中,这样值类型也可以存储在堆中

       我们这里不回列出所有的内置值类型,因为在第二章我们将要详细的讨论他们。在C#里,每一个预定义类型都会被编译器映射到相应的IL中的内置类型。同样在Visual Basic .NET.中也是这样的。

公共语言规范

       Common Language Specification (CLS)CTS一起以保证语言互操作性的实现。CLS是一个最小的标准集合,所有以.NET作为目标运行平台的编译器都必须遵守。因为IL是一个内涵丰富的语言,许多编译器的作者都喜欢实现IL CTS所提供的功能限制的一个子集。这样也好,只要编译器支持CLS所支持的所有定义就好。

       提示:你完全可以编写与CLS不兼容的代码。但是,如果你这样做,编译出来的IL代码就无法保证完全符合语言互操作性。

       比如,让我们来看一个关于字母大小写的例子。IL是区分大小写的。用区分大小写的语言来编写程序的开发人员习惯利用这种区分大小写的灵活性来给他们的变量命名。但是,Visual Basic .NET却是不区分大小写的。CLS的工作就是使CLS兼容代码明白,他们不能使用任何两个仅仅在大小写上有所区别的名称。这样,Visual Basic .NET就可以和任何CLS兼容代码协同工作了。

       这个例子说明了CLS有两种工作方式。首先,各个编译器功能不必非要强大到满足.NET的所有特性,这就鼓励了其他的编译器厂商将自己的语言加入到.NET这个大家庭来。第二,他也提供了这样的一个保证:如果你限制你的类使他满足CLS的兼容特性,那么其他的兼容语言编写的代码都可以使用你的类。

       这种方法的优点是使用CLS兼容特性的限制只适用于公共类和类的公有或者受保护成员。而对于那些你的类中的私有成员的实现,你完全可以编写任何非CLS兼容代码,因为其他的程序集的代码是无论如何也无法访问这部分的。

       在这里我们不需要详细的讨论CLS规范的细节。一般而言,因为C#中的非CLS兼容特性很少,所以CLS对你的C#代码影响不大。

1.3.3.2  垃圾收集

       垃圾收集器是.NET的内存管理方法,特别是针对如何回收运行的应用程序申请的内存的这个问题,垃圾收集器提供了一个好地解决办法。至今为止,针对如何释放进程动态申请的系统内存这个问题,在Windows平台上已经出现了两种技术。

       让应用程序代码自己手工实现内存释放

       让对象为呼应用数目

       让应用程序代码来负责释放内存是低级,但是高性能的语言采用的方法,比如C++。这种方法是高效的,他的优点是资源一旦不再需要就立即释放(一般情况下)。但是,他也带来了很大的缺点,由此而引发缺陷非常频繁。代码申请的内存当他们不再需要时当然应该明确的告知系统。但是,非常容易就忽略这一点,从来导致了内存泄漏。

       尽管现代开发环境确实提供了帮助探测内存泄漏的工具,但是因为此类问题只有到大量的内存泄漏以至于Windows拒绝批准进程申请内存时才会被发现,所以仍然有难以捕捉到的bug。从这一点上来说,由于对内存的需求,整个计算机就会逐渐的变的相当慢。

       COM采用维护应用计数的方法来解决这个问题。COM的方法是每个COM组件都维护一个计数器,这个计数器保存的是当前有多少客户机应用自己。当这个计数器的数值下降到零时,组件就会销毁自己并且释放相关的内存和资源。但是问题时COM的这种解决办法仍然是依靠客户机及时地通知组件他们已经使用引用完毕。只要有一个客户机没有及时销毁自己,那么组件就会留在内存中。但是从某种程度上讲,这种内存泄漏或许比C++的内存泄漏更为严重,因为COM对象会保存在自己的进程中而永远不会被系统删除(至少对于C++内存泄漏问题,系统可以在进程停止时重新收回所有的内存资源)。

       现在,.NET运行时改成依靠垃圾收集器来回收资源了。垃圾收集器就是一个专门用来整理内存的程序。方法是将所有动态申请的内存都分配到堆上(这对所有的语言都一样,但是在.NET中,CLR要单独维护自己管理的堆以供.NET应用程序使用)。情况常常是这样的,当.NET检测到分配给一个进程的托管堆已经变满因而需要整理时,他就会调用垃圾收集器。垃圾收集器就会扫描你的代码中的变量,检查托管堆上的对象的引用,以确定哪些个是现在还在使用的——也就是说哪些对象还有对自己的引用。任何已经没有你的代码引用的对象都会被认为是不再需要使用的,因此他们都将被移出。Java也有一个类似的垃圾收集器。

       .NET中的垃圾收集器本身就是为了适应进程的工作而设计的。其原则是:除非你复制一个已经存在的引用,否则你不可以为已经存在的对象建立引用,并且IL是类型安全的。这里的意思就是说,如果存在一个对对象的引用,那么我们就有足够的信息以明确的确定对象的类型。

       垃圾收集机制就不适合用在象非托管C++这样语言上,例如,C++允许指针在不同的类型中自由的转换。

       垃圾收集机制的一个重要的特点是它的操作时间是不确定的。换句话说,你不能保证垃圾收集器是何时被调用的,CLR觉得有必要调用它的时候他就会被调用(除非你明确调用它)。尽管你也可以不考虑这些过程而直接在你的代码中调用垃圾收集器。

1.3.3.3       安全性

因为.NET能够提供基于代码的安全性机制,所以.NET在安全性机制方面是优于Windows的,更何况Windows仅仅提供了基于角色的安全性。

基于角色的安全性机制是基于识别进程所运行的帐户的基础上的,换句话说,是谁拥有并运行这个进程?另一方面,寄予代码的安全性是建立在识别代码本身是如何执行以及其本身的可信度有多大的基础上的。幸亏IL提供了强大的的类型安全机制,CLR才能够在代码运行之前就预先检查代码的安全性以确定需要的安全许可。.NET也提供了一种机制,代码可以预先指出它需要运行在什么样的安全许可下。

基于代码的安全性的重要性是她可以降低运行来源不明的代码所冒的风险(比如你在Internet所辖在德代码)。例如,即便是代码是运行在管理员帐户下,基于代码的安全性仍然可以指出这段代码仍然不应该被允许执行某些管理员通常应该被允许的操作,例如读或写环境变量,读或写注册表,或者访问.NET反射特性。

安全性的详细问题我们将在14章讨论。

1.3.3.4       应用程序域

应用程序域是.NET的一项重要改革。运行的应用程序既需要彼此隔离又需要可以彼此通讯,应用程序域就是设计来解决这样一个棘手的问题的。这方面经典的案例是一个Web应用服务器同时需要响应许多的游览器的请求。因此,应用服务器可能就同时为了给不同的请求服务而运行许多的不同的实例。

.NET出现之前,有两种方法可以解决这个问题。一个是允许这些实例共享同一个进程,但是这样可能导致出现一个实例失败就会导致整个Web服务器关闭的结果。另一个方法是将这些实例彼此孤立在不同的进程中,但是这样也带来了性能的降低。

到目前为止,我们是通过将代码运行在不同的进程中来实现代码隔离的。当你启动一个应用程序的时候,他是在一个进程的环境中运行的。Windows系统通过地址空间来彼此隔离进程。具体的方法是每个进程都有4GB的虚拟内存用来存放他自己的数据和可执行代码(对32位系统来说是4GB,对64位的系统就会有更多)。Windows通过将虚拟内存与实际的物理内存或者磁盘空间建立映射关系来间接扩展虚拟内存的大小。每个进程都有不同的映射,而且虚拟地址空间块映射的实际内存都是不会相互重叠的(如图1-2所示)。

1-2

       一般情况下,进程只能通过虚拟内存中的特定地址访问物理内存,进程是不可以直接访问物理内存的。这样一个进程要想访问其他进程的空间就是不可能的了。这样也就保证了任何有错误行为的代码都不能损害自己地址空间之外的任何东西(注意,在Windows 95/98上,这种安全措施还不是象在Windows NT/2000/XP/2003平台上那样彻底,所以理论上还存在应用程序写入不适当的内存而导致Windows崩溃的可能性)。

       引入进程的目的不仅仅是用来彼此隔离运行代码的实例。在Windows NT/2000/XP/2003系统中,进程还是安全权限和许可的可分配单元。每一个进程都有自己的安全标志,这个标志告诉Windows什么样的操作是允许这个进程执行的。

       虽然进程对于安全性有巨大的帮助,但是它的最大缺点就是带来性能的损失。通常实际情况下许多进程需要在一起工作,因此他们就需要彼此通信。一个明显的例子是,有一个进程需要调用一个COM组件,而这个COM组件也是可以执行的,所以COM组件本身需要运行在自己进程中。在COM中使用代理也会有类似情况发生。由于进程之间是不共享内存的,所以一个复杂的工作就是在进程之间复制数据。这样做的结果就是带来了巨大的性能损失。如果你希望使组件协同工作而不希望有性能的损失,那你就必须使用基于DLL的组件技术,这样所有的代码都会运行在同样的地址空间里——当然,这样带来的风险就是,如果其中的一个组件运行出错,那么这个进程中所有的程序都会关闭。

       设计应用程序域的目的就是要在对性能毫无损失的情况下解决进程间共享数据的问题。具体的做法是,每一个进程都被分解到许多应用程序域中。每个应用程序域大体上对应于隐格单独的应用程序,每个执行的线程都运行在一个特定的应用程序域中(如图1-3所示)。

1-3

       如果不同的可执行代码在同样的进程空间里运行,那么显然他们可以方便的共享数据,因为理论上他们可以直接访问彼此的数据。尽管理论上这样是可行的,但是,在实际情况下CLR会检查每个运行的代码以保证这种情况不会发生,这样就保证了代码不会超出自己所拥有的区域执行操作了。乍一看这似乎是不可能的,毕竟如果你不去实际运行代码你又怎样知道代码究竟是怎样运行的?

       实际上,因为IL拥有强大的类型安全机制,所以这样做是可能的。在大多数情况下,除非代码使用了不安全的特性,比如指针,否则数据类型就会保证内存不会被不正当的访问。比如,.NET数组类型会执行边界检查以确保任何越界数组操作都是不允许的。如果一个运行的应用程序确实需要与不同的应用程序域的另一个运行的应用程序通信或者共享数据,那么它就必须通过调用.NET的远程服务的方式实现。

       这种验证代码以确保代码不可以超出应用程序域访问数据的机制就叫做内存类型安全(不同于通过外部远程调用机制)。这样处于一个进程中的不同应用程序域的类型安全代码就可以安全运行而互不干扰。

1.3.3.5       通过异常处理错误

Java C++所采用的异常处理机制一样,.NET Framework也设计为采用这种方便的基于异常的错误处理机制。C++开发者应该注意到,由于同样IL有强大的输入系统,所以在IL中以和C++同样的方式使用异常并不会带来性能损失。.NET C#还支持许多C++开发者期待的finally块。

我们将要在第11章详细讨论异常。简单的说,异常处理的方法就是专门指定一块代码来作为异常处理例行程序负责处理特殊的异常情况,每一块代码都可以处理特定的错误状况(例如,一个文件没有找到,或者某些操作被拒绝执行)。这种特定的错误状况可以根据你的需要而定制,你可以定的条件很窄也可以很宽。异常处理体系结构可以确保一旦有错误情况发生,程序就会立即跳到异常处理例行程序中那个处理此特定情况的代码块处执行。

异常处理体系结构还提供了一个方便的办法,你可以传递一个包含明确的异常信息的对象给一个异常处理例行程序。这个对象可能包含用户的特定消息和具体是代码的什么地方检测到了异常。

当异常发生时,大多数异常处理体系结构,包括程序流控制,都是由高级语言处理的(例如C#, Visual Basic .NET, C++),而不被任何特定的IL命令所支持。例如,C#利用代码块try { }, catch{ }, finally{ }来处理异常(详细情况请参看第11章)。

         但是,.NET也确实提供了一个基础结构,让所有以.NET为目标运行平台的编译器支持异常处理。特别是,.NET提供了一个.NET类的集合用来专门表示异常,并且语言互操作性允许异常处理代码处理抛出的异常对象而不管处理异常的代码使用什么语言编写的。这种语言无关性是C++ Java的异常处理机制都不能实现的,但是在COM的错误处理机制种有一定的体现,这包括从方法中返回错误代码以及传递错误对象。在不同的语言中保持异常处理的一致性对多语言开发来说是至关重要的一个环节。

1.3.3.6       属性的使用

C++编写COM组件的开发者一定对属性这个特性很熟悉(通过他们使用Microsoft’s COM Interface Definition Language [IDL])。属性最初的用意是为了给编译器提供关于某些程序相关的额外信息使用。

属性是由.NET支持的,因此现在C++, C#, Visual Basic .NET肯定也都支持。但是,.NET的属性有所创新,那就是它提供了一种机制,使你可以在你的源代码中定义你自己的属性。这些用户定义的属性将会被放置在数据类型或者方法所对应的元数据中。这些元数据可以用于文档的编制,他们可以和反射技术同时使用来实现基于属性的执行设计任务。另外,与.NET的语言无关性的原理一样,可以在源代码中用一种语言定义属性而用另一种语言将其读出。

我们将要在第10章讨论属性。

1.4              程序集

一个程序集是逻辑单元,它包含为.NET Framework所编译的代码。在这里我们不希望过多的关注程序集的细节,因为在第13章我们将要详细讨论,但是在这里概括他的要点。

程序集是完全自我描述的,而且是一个逻辑上的单元而不是物理上的,这也就表示他可以本存储在不只一个文件里(实际上动态程序集是存储在内存里的,而不是文件里)。如果一个程序集被存储在多个文件里,那么就会有一个主文件包含主要的入口点并且描述程序集中的其他文件。

应该注意到,对于可执行代码和库代码来说,他们的程序集的结构都是一样的。他们其中的唯一差别就是,可执行代码的程序集里包含一个主要程序出口点,而库代码的程序集里没有。

程序集的一个重要特性使它包含对应代码中定义的描述类型和方法元数据。但是,一个程序集也包含一些描述程序集自身元数据。这些包含在一个叫做程序集清单的程序集元数据,可以用来检查程序集的版本信息以及完整性。

注意:ildasm,一个基于Windows的使用工具,可以用来检查程序集的内容,包括程序集的清单和元数据。我们将在第13章讨论ildasm

事实上程序集包含程序的元数据意味着应用程序或者其他的程序集调用别的代码时,有了程序集所描述的信息,就不需要为了如何使用程序集而查询注册表或者其他的数据源了。相对于老的COM的做法,现在的这种做法有重大的突破。在以前的老方法中,组件的GUIDs和借口必须从注册表中获得,而且在某些情况下,方法和属性的细节还需要从类型库中读取。

将数据分散在最多可能3个位置上,这就意味着有些东西就有同步不好的风险,从而妨碍其他的软件成功地使用这个组件。通过使用程序集,因为所有的元数据都与程序的可执行指令存储在一起,所以就没有这种风险了。注意,即使是程序被分散存储在不同的文件里,同步数据也不会出现问题。这是因为包含程序集的主入口点的文件也同样存储着其他文件的细节、散列和内容,这就意味着如果其中的一个文件被替换了,或者以任何的方式发生损坏,也会被检测出来并且程序集会拒绝加载。

程序集有两种类型,私有程序集和共享程序集

1.4.1        私有程序集

私有程序集是使程序集中最简单的类型。他们一班附带于软件上面并且也仅仅被该软件所用。一般情况下附带私有程序集的情况是这样的,你在编写一个由一个可执行文件和好几个类库组成的应用程序,而类库中的代码你只想让这个应用程序使用。

系统保证私有程序集不会被其他的软件所使用,因为应用程序只可以加载与主可执行文件在同一目录或者其子目录的私有程序集。

由于我们一般认为商业软件总是会被安装属于他自己的一个目录里,这就是说没有软件包被以外覆盖,修改或者应用程序以外加载其他软件包的程序集的可能。因此,程序集只能够被自己所对应的软件包使用,所以对于什么软件使用他们你就有更多的控制权利。因此,不需要采取安全防范措施,根本就不会出现如其他的商用软件用一些新版本覆盖你的程序集的可能(专门设计用来执行恶意破坏的软件除外)。当然,名称也没有任何冲突。如果你的私有程序集的类和其他的人的私有程序集的类有相同的名称也不要紧,因为任何一个应用程序都只能够使用自己的私有程序集。

因为一个私有程序集是完全自包含的,所以配置他们的过程是非常简单的。在部署是你只需要简单的将应用程序文件目录系统的特定目录中就可以了(不需要注册注册表项)。这个过程被称为“零影响安装“(或者xcopy安装,或者是我们说的绿色安装)。

1.4.2        共享程序集

共享程序集被规定为任何应用程序都可以使用的公共库。因为任何软件都可以访问共享程序集。需要采取一些防护措施来避免如下的风险:

名称冲突,其他的公司的共享程序集执行类型和你自己的共享程序集有相同的名称。由于客户机代码在理论上存在同时访问两个程序集的可能,所以这是一个严重的问题。

存在一个程序基本不同版本的同一个程序集重写的可能,并且新版本可能与某些已经存在的客户机代码不兼容。

解决这些问题的办法是将共享程序集放置在文件系统中一个特定的目录子树中,被称作global assembly cache (GAC)。和私有程序集不同,不可以简单的将共享程序集复制到应用程序的目录中,它需要被专门的安装到缓冲区中。这个过程可以利用许多.NET工具来完成,包括对程序集进行特定的检查,也包括在程序集缓冲区中建立一个小的目录层次结构来确保程序集的完整性。

为了消除名称冲突的可能性,共享程序集都不赋予了一个基于私有密匙加密法加密的名称(私有程序集只需要简单的指定其主文件的名称为其名称就可以了)。这个名称被称作“强名“,保证唯一,并且每个引用共享程序集的应用程序都必须提供此名称。

程序集可能被覆盖的问题通过允许同时安装两个不同版本的相同程序集的时候,在程序集清单中记录特定的版本信息来解决的,

1.4.3        反射

因为程序集中存储了元数据,包括所有的类型和这些类型的成员的所有细节都在程序集定义了,所以就可以用程序访问元数据。这其中的所有细节我们将在第10章讨论。这就是可能令人感兴趣的被称作反射的技术,它说明托管代码可以检测其他的托管代码甚至是检测自身,已确定代码的信息。反射经常被用于获得属性的详细信息,当然,你也可以将反射技术用于其它目的,例如通过以字符串的方式提供类或者方法的名字来间接实现类或者调用方法。通过这种方法你可以在运行时根据用户输入才决定需要实例化的类或者需要调用的方法,而不需在程序编译之前就决定。

1.5        .NET Framework类库

至少从一个开发者的观点来看,编写托管代码的一个最大好处就是你可以使用.NET基础类库(base class library

.NET基础类就是一大堆已经编写好了的托管代码类的集合,几乎所有以前可以用Windows API来完成的工作她都可以帮你完成。这些类同样也遵守IL所采用的对象模型,基于单继承的对象模型。这就是说你既可以实例化任何一个需要的.NET基础类,或者你可以从其中派生你自己的类。

.NET基础类库的一个巨大的优点就是使用起来非常方便。例如,如果要启动一个线程,你可以直接调用Thread类的Start()方法。如果要销毁一个TextBox,你需要设计TextBox对象的Enabled属性为false虽然Visual Basic Java的开发者很熟悉这种使用方便的类库,但是对于C++开发者来说这已经带来了极大的解脱,毕竟多年来他们一直在使用像GetDIBits( ), RegisterWndClassEx( ), IsEqualIID( )这样的API函数,而且在其间需要传递很多的Windows handles

另一方面,C++开发者总是可以方便的访问整个Windows API,而Visual Basic 6 Java开发者通过各自的语言访问操作系统的基础功能就会受到很多限制。.NET基础类库的创新就在于它将类似Visual Basic Java类库使用的便利性和覆盖整个Windows API功能的全面性结合起来。当然,仍然有一些Windows的特性我们无法通过使用基础类库得到,必须使用API功能才能实现,但是,一般来说,那些使用不到的都是些特殊的特性。对于每天的应用而言,基础类库已经是足够的了。如果你确实需要调用API功能的话,不管你是在使用C#, C++, 或者 Visual Basic .NET.NET也提供了所谓“platform- invoke,以确保数据类型被正确的转换,这样这种调用的工作也没有在C++中直接调用函数困难。

注意:WinCV,一个基于Windows实用程序,可以用来游览基础类库中的类,结构,接口和枚举类型。我们将要在第12章讨论WinCV

尽管从题目上看第三章是用来介绍基础类库这个主题的,但是实际上,当我们完成了C#语言的语法学习后,这本书的主要任务就是教给你如何使用.NET基础类库中的各种各样的类了。这是一个非常广泛的基础类库!作为一个大致的介绍,.NET的基础类库包括:

       IL提供的核心特性(包括,基本数据类型和CTS具体见第3章)

Windows GUI的支持和控制(见第19章)

Web Forms (ASP.NET, 在第25 27章讨论)

数据访问(ADO.NET, 见第 21 22)

目录访问(见第24)

文件系统和注册表访问(见第30章)

网络和Web游览(见第31章)

.NET属性和反射(见第10章)

访问Windows操作系统(环境变量等,见第14章)

COM互操作性(见第2829章)

附带说明,根据微软的源文件,.NET基础类库中的大多数代码实际上都是用C#编写的!

1.5.1        命名空间

命名空间是.NET消除类和类之间名称冲突的办法。他是用来避免如下状况的:你定义一个表示消费者的类,并且给她取名叫做Customer而正好有另一个人也是这样做的,这样就引起了冲突(想象一下,在一个有很多消费者的商业中)。

命名空间只是一组数据类型,但是命名空间里的数据类型会自动在自己的名字前加上命名空间的名字作为前缀。而且命名空间是可以互相嵌套的。比如,大多数用于一般目的的.NET基础类都被放在一个称为System的命名空间里。基础类Array就在这个命名空间里,所以数组的全名是System.Array

.NET要求所有的数据类型都要定义在命名空间里,例如你可以将你的Customer类放在一个叫YourCompanyName的命名空间里。这样这个类的全名就是“YourCompanyName.Customer“。

注意:如果没有明确地提供命名空间,那么定义的类型就会被添加到一个没有名字的全局命名空间里。

微软建议,对于大多情况,你应该指定一个至少两层嵌套结构的命名空间名称:第一个代指你的公司名称,第二个用来指代类所存在于的技术或者软件包的名称,例如YourCompanyName.SalesServices.Customer这样就保证了在大多数情况下你的应用程序的类都不会和其他的组织的类发生名称冲突。

我们将要在第2章更加详细地了解命名空间。

1.6            C#创建.NET应用程序

C#当然可以用来创建控制台应用程序,一种在DOS窗口下运行的仅有文本的应用程序。你可能会在单元测试类库时用到控制台应用程序,或者在创建Unix 或者 Linux daemon进程时用到。但是,在更多的情况下你将会用C#创建一些利用更多的.NET技术的应用程序。在这一节,我们将要大体了解一下C#究竟可以创建哪些不同类型的应用程序。

1.6.1        创建ASP.NET应用程序

Active Server Pages (ASP)是微软用来创建带有动态内容的Web的技术。一个ASP页面基本上就是一个在HTML中加入大块运行在服务器端的VBScript 或者 JavaScript代码的文件。当一个客户端游览器请求一个ASP页面时,Web服务器处理服务器端代码并连同HTML部分一起发送给客户端。通常,这些脚本会从数据库里查询数据并且用HTML将这些数据标记出来。对于客户来说,ASP是一种创建基于游览器的应用程序的简便方法。

但是,ASP也不是没有缺点。首先,ASP页面显示起来比较慢,因为服务器端代码是解释运行的,而不是编译运行。第二,ASP文件很难被维护,因为他们是无结构的,服务器端ASP代码和普通HTML代码完全混合在一起。第三,有时候开发ASP也是困难的,因为ASP对错误处理和类型检查支持很少。

特别的,如果你希望在你的页面中用VBScript实现错误处理,你就必须使用On Error Resume Next声明,并且跟踪检查Err.Number以确定每个组件的调用有运行正常。

ASP.NET是一个全新版本的ASP,并且修正了的许多问题。ASP.NET并不是为了取代ASP,相反,ASP.NET页面可以和原来的ASP应用程序一起存在于同一个服务器上。当然,你可以用C#编写ASP.NET应用程序。

后面的小节我们将会探索一下ASP.NET的关键特性。如果想要获得更多的细节,请参考第2527章。

1.6.1.1    ASP.NET的特性

首先,也是最重要的,ASP.NET页面是结构化的。这就是说,每一个页面都是一个继承自.NET System.Web.UI.Page的有效类,而且你可以重写一些方法,这些方法会在整个页面的生存其中调用(你可以认为这些方法就像原来在ASP下的global.asa里的OnApplication_Start OnSession_Start事件差不多)。因为你可以直接将页面的功能直接放在事件处理程序中实现,ASP.NET页面很容易理解。

ASP.NET页面的另一个优点是你可以用Visual Studio .NET创建它,你也可以用同样的开发环境创建ASP.NET页面所用到的商业逻辑和数据访问组件。一个Visual Studio .NET项目或者解决方案包含一个应用程序相关联的所有文件。而且,你还可以在这个编辑器里调试你的ASP页面,这可比原来的Visual InterDev要好得多。配置InterDev和工程的Web服务器以令他们支持调试是一件令人头痛的事情。

最清楚的是,ASP.NET的后置代码特性可以使你更进一步的结构化你的方法。ASP.NET允许你将服务器端的功能于页面代码隔离开来,单独放到一个类中,这个类会被编译成DLL文件,并且这个DLL会被放置在HTML部分的下级目录中。在页面的顶不会有一个后置代码的指示将文件和它的DLL连接起来。当游览器请求一个页面的时候,Web服务器就会再页面的后置代码所产生的DLL文件中引发类中的相应事件。

最后,但是也很重要,ASP.NET带来的性能增加是显著的。相比较经典的ASP页面都是解释性相应每个请求而言,Web服务器缓存的是经过编译后的ASP.NET页面。这意味着以后对于ASP.NET页面的请求执行起来就会比以前快得多。

ASP.NET同样也使编写基于游览器的窗体变得简单,这些窗体你可以在企业内网中使用。传统的基于窗体的应用程序提供一个功能丰富的用户界面(富客户端),但是因为这些用户界面程序运行在不同的机器上,所以很难去维护它们。正因为这个原因,当人们既需要一个功能强大的用户界面又需要为用户提供广泛的支持的时候,人们就会依靠基于窗体的应用程序。

但是,随着IE5的到来和Navigator 6的没落,基于窗体的应用程序的优点越来越不明显。IE 5DHTML一致并且全面的健全的支持,允许程序员创建同原来富客户端环境完全相同的基于Web的应用程序。当然,这样的应用程序在IE是必要的并且标准的,但是Navigator却不支持。在许多工业情况中,标准化现在是正常的。

1.6.1.2    Web Forms

为了使建立Web页面更加容易,Visual Studio .NET提供了Web Forms它们允许你用像创建Visual Basic 6 或者 C++ Builder窗口的方式图形化的建立ASP.NET页面,通过那种拖动一个toolbox空间放到窗体中的方式,相应的代码就会出现在窗体中,最后你只需要为每个空间的事件编写程序就可以了。当你用C#创建Web窗体的时候,你实际上是在创建一个继承自Page基类的C#类,并且某个ASP.NET页面指定此类是其后置代码。当然,你创建Web窗体不必非采用C#,你也可以使用Visual Basic .NET或者其他的.NET语言都无妨。

在过去,开发Web应用程序的困难使得很多团队都不喜欢开发Web应用程序。如果你想要成功开发Web应用程序,你就必须掌握很多不同的技术,比如VBScript, ASP, DHTML, JavaScript等等。通过将窗体的概念应用于Web页面,Web使得Web开发变得相当的简单。

Web 控件

       Web窗体使用的控件与ActiveX控件不同。它们是ASP.NET命名空间中的一些XML标签,在请求一个页面时,Web游览器动态的将其转换为HTML和客户端脚本。令人惊奇的是,Web服务器可以有好几种方法执行服务器端的控件,根据请求着的游览器的不同而做出相应的转化。这就意味着现在为Web页面编写通用的用户界面是非常简单的,而不用担心你的页面将会运行在什么样的游览器上,因为Web替你考虑这一切。

你可以使用C# 或者 Visual Basic .NET来扩充你的Web窗体工具箱。建立一个新的服务器端控件事非常简单的事情,实现.NETSystem.Web.UI.WebControls.WebControl类就可以了。

1.6.1.3    XML Web服务

今天,HTML页面占据了World Wide Web的大多数应用。但是,通过XML计算机有了一种独立于设备的在Web上相互通信的方式。将来,计算机可以利用Web XML来交换信息而不是采用专用线路和专有的格式,例如Electronic Data Interchange (EDI)XML Web服务是为面向服务Web而设计的,在最终显示给用户之前,远程计算机可以互相提供动态的信息并且分析和重新格式化已提供给用户。XML Web服务以XML的形式提供了一种计算机之间互相提供信息的简便方法。

在技术上说,.NET上的XML Web服务就是一种返回XML而不是HTML给客户端的ASP.NET页面。这样的页面饱含一个派生自Web Service类的后置代码的DLLVisual Studio .NET IDE提供了一个简化Web服务开发的引擎。

一个组织主要有两个理由采用XML Web服务。第一个原因是XML Web服务是依赖于HTTP的,XML Web服务可以利用现有的网络来作为信息传输的媒介。另一个原因是XML Web服务所使用的数据格式XML是自我描述的、非专用的、平台独立性的。

1.6.2       创建Windows Forms

尽管C# .NET非常适合开发Web开发,但是他们仍然为开发所谓的胖客户端或者富客户端应用程序提供了良好支持,这些程序都必须被安装到最终用户的机器上并且在那里执行。这些功能都是由Windows Forms提供的。

对于类似Visual Basic 6下的那种窗体,在.NET下面就是Windows Form。要设计一个图形化的窗口界面,你只需要将控件从工具箱拖拽到Windows Form就可以了。要决定窗口的行为,你可以为Form中的控件编写事件处理程序。一个Windows Form项目将会被编译为一个可执行程序,但是最终用户的计算机上必须安装有.NET运行时才可以运行这个程序。像其他的.NET项目类型一样,Visual Basic .NET C#都可以创建Windows Form项目。我们将在第19章在详细介绍Windows Form

Windows 控件

       尽管Web Forms Windows Forms的开发方式在许多方面一样,但是你创建他们所用到的控件是不同的。Web Forms 使用 Web 控件创建,而Windows FormsWindows控件。

       Windows Control比较像ActiveX control。当一个Windows control被实现后,他被编译为一个DLL并且必须被安装到客户端的机器上才能使用。事实上,.NET SDK提供了一个可以为ActiveX control创建包装的工具,这样ActiveX control就可以在Windows Forms上使用了。同Web Controls的情况一样,创建一个Windows Control也需要从一个特定的类System.Windows. Forms.Control继承。

1.6.3       Windows服务

一个Windows Service(最初被称作NT Service)就是一个被设计用来在Windows NT/2000/XP/2003(不包括Windows 9x)后台运行的程序。如果你想要创建一个程序,这个程序需要连续运行,随时相应发生的事件并且不需要用户手动运行该程序,那么这种程序就是Windows Service。一个很好的例子就是Web 服务器上的World Wide Web Service程序,它就是用来监听来自客户端Web请求的Service

C#中建立Windows Service是非常简单的。.NET Framework的基础类库的System.ServiceProcess命名空间提供了很多用于处理与服务相关工作的样板文件。另外,Visual Studio .NET允许你创建C# Windows Service 工程,这是用C#作为源代码创建基本Windows service的方法。我们将会在第32章详细介绍如何用C#编写Windows Services

1.7            C#.NET企业架构中的角色

C#程序的运行需要.NET运行时的支持,但是.NET运行时要想在大多数客户端安装、普及,特别是大多数家用电脑,可能还需要少数几年的时间。在这期间,安装C#应用程序可能就意味着也需要安装.NET可重新分布的组件。正因为如此,我们通常将会看到许多C#应用程序首先会被应用到企业环境中。实际上,C#为那些对建立健壮的、N层的客户端-服务器应用程序感兴趣的组织提供了一个很好的机会。

当与ADO.NET结合后,C#就有快速的和经常性的访问数据库的能力,如SQL ServerOracle数据库。访问数据库返回的dataset可以非常容易的用ADO.NET对象模型操作,并且可以自动将其转化为XML以便于在办公内部网传输。

    一旦新工程的数据库规划已经确定,C#提供了一个极好的媒介来实现数据访问对象层,每个媒介都提供了插入、更新和删除不同数据库表的方法。

因为是第一个基于组件的C语言,C#也非常适合于实现商务对象层。C#将中间层内部组件通讯的复杂性给封装起来,这样开发者就可以腾出时间来将精力集中于在方法中粘合他们的数据访问对象到一起,使他们的方法可以更确切的执行组织的业务逻辑。并且通过属性,C#业务对象可以配备方法级别的安全检查,对象池,和由COM+服务提供的JIT活动。另外,.NET附带的实用程序允许你创建的新的.NET业务对象与遗留下来的COM组件交互。

要用C#创建企业应用程序,你可以创建一个包括数据访问对象的和另一个包括业务对象的类库项目。在开发中你可以用控制台项目来测试你的类的方法。极限编程的爱好者可以建立控制台项目来自动执行从一批文件到整个单元的测试,而中间不会被打断。

    注意,C# .NET都可能影响你的可重用类的物理封装方式。在过去,许多开发者将许多类放到一个单一的物理组件中,因为这样可以使部署容易得多;而且如果出现了版本问题,你知道到哪里去检查。因为配置.NET企业组建仅需要简单的将文件复制到目录中就行了。开发者现在可以按照更加更合乎逻辑的、离散的组件方式来封装自己的类,而不会出现“DLL Hell”问题。

    最后,但是同样重要的是,基于C#ASP.NET页面为建立用户界面提供了一个极好的媒介。因为ASP.NET页面是经过编译的,所以他们执行起来很快。因为他们可以在Visual Studio .NET IDE中调试过的,所以他们是健壮的。因为他们支持全面的语言特性,例如早期绑定、继承和模块化,基于C#ASP.NET页面的代码是整洁的,易于维护的。

    经验丰富的开发者对大做广告的新技术或者语言都持怀疑态度,而且很难仅仅因为推荐怂恿就勉强采用一个新平台。如果你是一名在IT部门的企业开发者,或者你仅是通过World Wide Web提供应用程序服务,C# .NET至少提供了四个可靠的好处,这还不包括一些比较奇异的特性,如XML Web services和服务器端控件:

组件冲突将会变得很少见,部署也会变得更简单,因为同一个组件的不同版本可以在同一个机器里面并行运行而不会产生冲突。

你的ASP.NET代码不再会晦涩难懂。

你可以在.NET基础类库中调控很多的功能性。

对于需要一个Windows Forms用户界面的应用程序来说,用C#编写此类程序非常容易。

    由于Web Forms和基于因特网的应用程序的出现,最近几年里Windows Forms在一定程度上受到了忽视。但是,如果你和你的同事在JavaScript, ASP, 或者其他相关技术方面缺乏经验,Windows Forms就仍旧是一个可行的快速并且方便的创建用户界面的选择。记住要把你的代码分层实现,这样用户界面才能和业务逻辑以及数据访问代码分开。这样做的目的可以让你在未来的某个需要的时候将应用程序迁移到游览器上来。当然,Windows Forms将继续在家庭以及小规模商务应用的用户界面技术上保持统治地位。

1.8        小结

    在这一章我们介绍了很多基础知识,让我们简要回顾一下.NET Framework知识的重要方面以及他与C#的关系。我们首先讨论了为何所有面向.NET的语言是先被编译成微软中间语言(IL)的,因为只有这样他们才可以被Common Language Runtime (CLR)编译成本地代码并且执行。我们还讨论了一下这些.NET的特性在编译和执行中所起到的作用:

程序集和.NET基础类

COM组件

JIT编译

应用程序域

垃圾收集器

1-4展现了这些特性在编译和执行中是怎样发挥作用的:

1-4

我们还讨论了IL的特性,特别是强数据类型和面向对象的特性。我们讨论了这些特性是如何影响面向.NET的语言的,包括C#。我们还讨论了强类型特性是如何给与IL语言互操作能力的,和CLR所提供的诸如垃圾回收以及安全等特性。

最后我们讨论了如何用C#创建包括ASP.NET在内的基于.NET技术的应用程序。

我们将在下一章介绍如何编写C#代码。

转载地址:http://iqjhb.baihongyu.com/

你可能感兴趣的文章
ansible 视频_Jeff Geerling的Ansible 101视频以及更多Ansible新闻
查看>>
git meld不支持_不爱差异吗? 改用Meld
查看>>
为什么我从Mac切换到Linux
查看>>
spring 引入zuul_引入Zuul改进CI / CD
查看>>
使用bash默认环境_使用Bash炸鱼壳以获得漂亮的默认设置
查看>>
net开源开发web框架_我的6大Web开发开源框架
查看>>
PDF的Adobe Acrobat的开源替代品
查看>>
python传递数据_使用此消息传递库在C和Python之间共享数据
查看>>
自动化脚本自动化执行_5个用于自动化基本社区管理任务的脚本
查看>>
helm 和kubectl_适用于初学者的基本kubectl和Helm命令
查看>>
linux防火墙_Linux防火墙入门
查看>>
matlab 替代品_MATLAB的4种开源替代品
查看>>
minecraft1.5_Minecraft的5种开源替代品
查看>>
微软 开源 控制台_使用此开源窗口环境一次运行多个控制台
查看>>
tmux 嵌套 tmux_使用tmux和kubectl的功能对Kubernetes进行故障排除
查看>>
如何创建工件坐标系_自动创建研究工件
查看>>
针对CI / CD管道和Windows用户的提示以及更多Ansible新闻
查看>>
ansible剧本如何写_我学过的3课:写Ansible剧本
查看>>
bash 脚本部署lmnp_使用Bash自动化Helm部署
查看>>
linux 中移动文件_如何在Linux中移动文件
查看>>