您的位置:首页 > 编程语言 > C#

C#学习笔记——软件构建与.NET平台

2012-01-03 16:23 267 查看
目录

一 软件构建,智者的积木

二 语言没有“银弹”正如世间难寻“圣杯”

三 .NET平台结构

一 软件构建,智者的积木

  多年来,众多的业界巨子给软件构建做出了不同的比喻,虽然你可能并不认识他们,也不打算了解这些巨人的英雄史诗,但是你应该了解他们对软件构建的看法,毕竟他们才是这场游戏中的高级玩家。我并不想讨论这些隐喻是否合理,我只希望以下隐喻能够帮助你重新思考下软件构建活动。

  写作 这一隐喻暗示着软件开发过程是一种以编写代码为主的代价昂贵的试错过程,而非仔细的规划和设计。

  耕种 这一隐喻暗示了软件就想是耕种一样,你每次只处理它的一小部分,一点一点的加到整个系统,使系统一点一点的“生长”。它也暗示了,工作应该按部就班,正如春种秋收一样,各个环节有强硬的逻辑存在。

  盖房子 这一隐喻形象的说明了规划和设计在构建活动中的重要性,同时也暗示了软件在初期修复缺陷的成本要远远低于在后期的修复成本。

  焦油坑 经典书籍《人月神话》的第一章提到的:“大型系统的开发就犹如这样一个焦油坑,很多大型和强壮的动物在其中剧烈的挣扎,他们中大多数开发出了可运行的系统——不过只有极少数的项目满足了目标、进度和预算的要求。”

  不管你是否同意以上观点,它们确实在一定时期影响了一部分人。而我想从一名程序员的角度来说我对软件构建的认识。我觉得软件构建过程对于程序员来说,就像是在玩乐高积木。众多的语言给我们提供了形形色色的API,就如同不同形状、颜色的积木块。业界标准构成了积木之间的接点,使积木块之间无缝衔接。单个的小积木通过接点组合成你想要的任何形状和功能的大的模块,模块和模块之间再相互组合,形成更大的系统。软件构建与玩积木两者都是考验脑力的智力活动,你可以按照图纸玩积木(别人设计的),也可以用积木来拼装你想要的任何东西(自己做设计)。(ps.我小时候用乐高的“潜水艇”积木拼出了星际I中的“歌利亚”。)也就是说,语言、平台就在那里,你能做出什么,就看你能想到什么,以及你的团队能实现什么。

  软件构建是一项十分复杂的工作,绝不是一个人的工作,而是一件团队活动。下面,给出软件构建的最普遍活动:

定义问题

需求分析

规划构建

软件架构

详细设计

编码与调试

单元测试

集成测试

集成

系统测试

保障维护

  以上活动在整个软件构建过程中会反复、交替进行,这些活动仅仅是基于技术的产品导向过程,而没有包括相应的管理活动。对于程序员来说,编码与调试往往占据了我们的大部分时间。而其他活动随项目的类型和规模,往往有所“裁剪”。可见,软件构建过程十分复杂,正因为其复杂性,人们为了确保软件项目的成功,试图寻找一套结构化方法。但是到目前为止,也没有找到能够保证软件项目100%成功的方法。虽然软件项目中,“圣杯”并不存在,但是我们仍然得到了相当有用的方法或指南,以提高项目成功几率,并使这种成功得以再现,例如软件工程理论,PMI制定的项目管理指南(PMBOK)以及敏捷开发的相关思想等等。

二 语言没有“银弹”正如世间难寻“圣杯”

  如果把开发语言当成一门外语的话,那么我们程序员无疑掌握了众多的“外语”,即使外语翻译也望尘莫及。正如外语一样,不同语言有不同的特点,要造就了不同的阵营与拥护者。在不同阵营,并相互抨击的时候,你是否还犹豫不决,是否又有转移方向的冲动?某位业界大牛说过,好的程序员要掌握3类语言:一、快速开发的语言;二、吃饭的语言;三、最钟爱的语言。这只是3类语言,并不是说3种。你完全可以学习一门语言并说它在这3个方面都很出色。(ps.目前我看PHP有这个苗头。)还有一种说法就是“学得多,学的浅“。不管你是想深入掌握一门语言还是要广泛了解多门语言,你一定要认识到开发语言没有所谓的“银弹”,或许某些语言能独挡一面,当没有哪种语言是全才,其必有优势与劣势,而且随着时间的推移,今日风光的语言,明日可能就会被淘汰。如果你钟爱一门语言就不要被路人轻易打动而另投他人门派。学习语言是一件持之以恒的事情,当你想改换门派的时候不妨问问,这门语言真的好吗,你真的深入理解了你现在的语言了吗,它真的是你想要的吗?编程之真谛在于思想,透过语言编程才是正确的道路。

  每个人的经历不同可能会有不同的语言基础,我在众多语言之中选择了C#,我并不认为C#对所有人都适合,但它的诸多优点让我难以放弃。以下是我选择C#的理由。

  .Net平台非常强大 .NET平台提供了丰富的类库,涵盖了几乎所有领域的应用,不需要借助其它语言,就能开出几乎所有的应用。当然,大量的API也需要我们不断的学习。

  强大的开发环境 Visual Studio 可谓是IDE中的王者,用了之后就会被其强大的功能所吸引,在结合微软的TFS,在软件项目管理中将发挥出极大的作用。

  丰富的资源 微软的MSDN,微软社区还有大量的参考书籍,及庞大的用户群,造就了一个庞大的资源宝库,你身处于这样庞大的群体之中,能够更快更容易的掘取知识。

  入门简单 C#本身就是一门面向对象的高级语言。相比于低级语言,其语法和代码可读性更高,更容易让人接受,你完全可以通过书籍自学成才。

  开发周期快 C#或者说.NET的类库已经为我们做了大量的工作,相比其它语言,你的代码量更少,你开发系统的周期更短,无论是大型项目还是小型项目你都会得益于这种快速的开发。当然,开发周期短也得益于Visual Studio 的强大功能。

  与各种微软产品的无缝集成 你可以和微软的其它产品例如SQL Server,Office,Sharepoint,OC等无缝集成。

  当然,C#也有一些劣势:

  不跨平台 C#只能用于开发.NET程序,虽然.NET被设计为平台无关,但目前还无法在Linux等平台上稳定运行.NET程序,虽然有个开源的Mono,但其可靠性有待测试。这会让你失去很大的发挥空间,即使你能构建客户需要的软件,如果客户坚持使用Linux系统你也只能放弃。或许有那么一天,.NET可以用于其它平台,但现在我们还是在Windows平台玩吧。

  开源资源相对较少 相比其他语言,用C#写的开源项目还是少数,有些时候你只能自己写框架了。

  综上所述,语言没有“银弹”,如果你享受C#给你带来的便捷,同时接受了它的不足,并决定深入学习的话,我希望下面的文章能对你有所帮助。

三 .NET平台结构

  从程序员的角度看,.NET可以理解为一个运行库环境(mscoree.dll)和一个全面的基类库(mscorlib.dll)。如下图所示:



(一)基类库

  .Net Framework 中包含了Framework 类库(Framework Class Library,FCL),有些书籍中也称为BCL,微软正以惊人的速度在完善FCL,为我们构建大型系统提供便利。如果想全面了解FCL,我推荐的书籍是《C#高级编程》,目前已经出到第七版了。该书以页数和全面著称,但不足之处在于,其内容比较基础,要想深入学习,需结合其它书籍。FCL十分庞大,而且在不断完善中,最好有针对性的学习某一分支,避免将精力分散。

(二)公共语言运行时

  运行时(runtime),可以理解为执行给定编译代码单元所需的外部服务的集合。如上图所示,.NET的运行时叫CLR(Common Language Runtime),即公共语言运行时,是一个可由多种开发语言使用的“运行时”。CLR不关心使用何种开发语言,只要相应的开发语言的编译器面向CLR即可。微软已经创建了几个面向CLR的语言,包括:C++/CLI、C#、VB、F#、Python、Ruby,以及自己的编译器。其它机构也制定了一些面向“CLR”的开发语言和编译器。但是,使用最广泛的还是微软的C#语言。CLR的核心功能包括:内存管理,程序集加载,安全性,异常处理和线程同步等等。通常在CLR的控制下运行的代码称为托管代码。源代码的编译和执行过程如下:



1 源代码编译

  编译的第一步——把源代码编译为托管模块(其中,C++的编译器比较特殊,它能同时生成托管和非托管代码,并生成到同一模块中。)

  托管模块 托管模块是一个标准32(64)位Windows可移植载体PE32(PE32+)文件,需要CLR才能执行。托管模块的组成如下:



  元数据 元数据是一组数据表。其中一些数据表描述了模块中定义的内容,比如类及其成员。还有一些元数据描述了托管代码模块引用的内容,比如引用的类及成员。 这些元数据描述了每一个二进制文件中定义的类型(类、结构、枚举等),以及每个类型的成员(属性、方法、事件等)。Visual Studio 等开发工具通过元数据实现智能感知,而元数据也成为包括WCF、Web Service、反射、晚期绑定和对象序列化等技术的支柱。

  MSIL Microsoft Intermediate Language (MSIL)微软中间语言,也被叫做CIL(公共中间语言),或者简称IL。其本质上是.NET平台的母语。任何一种支持.NET的语言在逻辑上都需要支持IL的,也都会被编译为IL。

  IL具有以下特征:

面向对象和使用接口

值类型和引用类型之间的巨大差异

强数据类型

使用异常来处理错误

使用特性

  IL的好处:

语言集成性——每种支持.NET的语言生成的是几乎相同的IL

平台无关性

  我们不需要直接使用IL来编写程序,但是在.NET中,使用System.Reflect.Emit这个命名空间提供的类型可以在开发出在运行时能够在内存中产生.NET程序集的程序,即“动态程序集”。由于在构造程序集时需要使用专有的IL指令集,所以如果要使用这部分功能开发软件,需要掌握IL。

  编译的第二步——将托管模块合并为程序集

  CLR不和模块一起工作,它只和程序集一起工作。程序集是一个或多个模块/资源文件的逻辑性分组,是重用、安全性及版本控制的最小单元。通过程序集的概念,我们可以把一组文件当做一个文件来对待。将托管模块合并成程序集的过程如下:



  程序集包含的足够的信息,使其具有自描述性。CLR能判断出为了执行程序集中的代码,程序集的直接依赖对象是什么,不需要注册表或 Active Directory Domain Services(ADDS)中保存额外的信息。所以,相较于非托管组件,程序集更容易部署。

2 通用类型系统与公共语言规范

  公共类型系统与公共语言规范是,是.NET实现语言互操作性的基石。语言互操作性的真正含义是用一种语言编写的类应该能直接与另一种语言编写的类通信,特别是:

用一种语言编写的类应该能继承用另一种语言编写的类。

一个类应该能包含另一个类的实例,而不管它们是使用什么语言编写的。

一个对象应该能直接调用其它语言编写的另一个对象的方法。

对象应该能在方法之间的传递。

在不同的语言之间调用方法时,应能在调试器中调试这些方法调用,即调试不同语言编写的源代码。

2.1 CTS

  类型(type)指的是集合{类,接口,结构,枚举,委托}里的任意一个成员。CLR完全是围绕类型展开的,微软制定了一个正式的规范,即"通用类型系统"(Common Type System,CTS),它描述了类型的定义和行为。CTS规定,一个类型可以包含零个或多个成员,制定了类型的可视性规则和类型成员的访问类型,为类型的继承、虚方法、对象生存期定义了相应的规则。我们无论使用哪一种语言,类的行为都是完全一致的,因为最终是由CTS来定义类的行为。

  CTS定义了5种类型:

类类型

接口类型

结构类型

枚举类型

委托类型

  上述5种类型包含众多的类型成员即集合{构造器,析构器,静态构造函数,嵌套类型,运算符,方法,属性,索引器,字段,只读字段,常量,事件}中的元素之一。

  CTS还定义了一个内容丰富的类型层次结构,其中包含设计合理的位置,在这些位置上,代码允许定义它自己的类型。CTS的层次结构如下:



  此外,CTS建立了一套定义明确的核心数据类型,以字符串类型为例:String(VB.NET)、string(C#)、String^(C++/CLI)最终被解释成为CTS数据类型System.String。

2.2 CLS

  为了能适用多种语言,微软定义了一个“公共语言规范”(Common Language Specification,CLS),它详细描述了一个最小的功能集,任何编译器生成的类型想要兼容由其他符合CLS、面向CLR的语言所生成的组件,就必须支持这个最小功能集。由于IL是一种丰富的语言,大多数编译器的编写人员有可能把给定编译器的功能限制为只支持IL和CLS提供的一部分特性。对于CLS,首先是各个编译器的功能不必强大到支持.NET的所有功能;其次,CLS提供了如下保证:如果限制类只能使用CLS兼容特性,就要保证其它兼容语言编写的代码可以使用这个类。编写非CLS兼容代码是完全可以接受的,只是在编写了这种代码后,就不能保证编译好的IL代码完全支持语言的互操作性。也就是说,在开发类型和方法的时候,如果希望它们对外“可见”,能够从符合CLS的任何一种编程语言中访问,就必须遵守由CLS定义的规则。用一种语言定义一个类型时,如果希望在另一个语言中使用该类型,就不要在该类型的 public 和 protected 成员中使用位于CLS外部的任何功能。否则,其它其它语言可能无法正常访问这个类型的成员。以下代码定义了一个不符合CLS的类型:

using System;
//检查CLS相容性
[assembly:CLSCompliant(true)]

namespace CLRTest
{
//因为是public类,所以验证相容性
public sealed class Test
{
//返回类型不符合CLS
public UInt32 get()
{
return 0;
}

//仅大小写不同标识符,不符合CLS
public string Get()
{
return "0";
}

//私有方法可以不符合CLS,不会显示警告
private UInt32 GET()
{
return 0;
}
}
}


  [assembly:CLSCompliant(true)]特性应用于程序集,这个特性告诉编译器检查 public 类型是否符合CLS规范。这种方法的优点是使用CLS兼容性的限制只适用于公共和受保护的类的成员和公共类。在类的私有实现方式中,可以编写非CLS代码,因为其它程序集中的代码不能访问这部分代码。

  CLS的基本规则是“一个类型的每个成员要么是一个字段(数据),要么是一个方法(行为)。”编译器遇到枚举、数组、属性、索引器、委托、事件、构造器、析构器、操作符重载、转换操作符等任何一种构造,必须将其转换成字段和方法,使CLR和其它语言能够访问这些构造。(注:CLR的完整规则列表,请参考http://msdn.microsoft.com/zh-cn/library/a2c7tshk.aspx。)

3 加载CLR

  生成的程序集既可以是一个可执行应用程序,也可以是一个DLL。理论上,任何基于IL的程序集都可以在任何CPU上运行,但是实际上,程序集有可能是不可移植的(64位程序不能运行在32位平台上)。如果IL中没有与特定CPU架构或机器语言相关的内容,JIT编译器就会在运行时为目标CPU生成机器指令。如果开发一个需要特定CPU架构的程序集,我们必须把CPU信息告诉VS,以便它将信息合并到二进制文件中。运行一个可执行文件时,Windows会检查这个EXE文件的头,判断应用程序是32位的还是64位的(64位Windows提供了WoW64技术,运行运行32位程序,但有性能损耗)。Windows还会检查头文件中嵌入的CPU架构信息,确保当前计算机符合要求。Windows检查后完EXE文件的头,决定创建32位、64位还是WoW64进程之后,会再进程的地址空间中加载mscoree.dll(CLR中最重要的部分,称为“公共对象运行库执行引擎”,它包含大量核心类型,它们封装了各种常见的编程任务与核心数据类型)的x86、x64或IA64版本。然后进程的主线程调用mscoree.dll中定义的一个方法。这个方法初始化CLR,加载EXE程序集,然后调用其入口方法(Main)。随即,托管的应用程序将启动并运行。

4 执行程序集的代码

4.1 程序集执行过程

  如前所述,程序集包含元数据与IL。为了执行一个方法,首先必须把IL转换成本地CPU指令。这是CLR的JIT(just-in-time,即时)编译器(也叫“JITter”)的职责。下图展示了,方法在被调用时,发生的事情:



  在Main方法执行之前,CLR会检测出Main的代码引用的所有类型。这导致CLR分配一个内部数据结构,它用于管理对所有引用的类型的访问。上图中,Main方法引用了一个Console类,这导致CLR分配一个内部结构。在这个内部结构中,Console类定义的每个方法都有一个对应的记录项(entry)。每个记录项都容纳了一个地址,根据此地址即可找到方法是实现。对这个结构初始化时,CLR将每个记录项都设置成(指向)包含在CLR内部的一个未文档化的函数,即上图中的JITCompiler(《CLR Via C#》中使用的名称)。Main方法首次调用WriteLine时,JITCompiler函数会被调用。JITCompiler函数负责将一个方法的IL代码编译成本地CPU命令,根据运行环境不同,JIT编译器会生成相应的x86、x64或IA64指令。然后,JITCompiler函数会在定义(该类型的)程序集的元数据中查找被调用的方法的IL。接着JITCompiler验证IL代码,并将IL代码编译为本地CPU命令。本地CPU命令被保存到一个动态分配的内存块中。然后JITCompiler返回CLR为类型创建的内部数据结构,找到与调用方法对应的那一条记录,修改最初对JITCompiler的引用,让他现在指向内存块中的代码(WriteLine(string)的具体实现)。这些代码执行完毕并返回时,会返回到Main中的代码,然后继续执行。

  当执行到Console.WriteLine("World");时,Main要再次调用WriteLine(string)。这时,因为之前已经对WriteLine(string)的代码进行了验证和编译,所以会直接执行内存块中的代码,WriteLine("World")执行完毕后,再次回到Main。

4.2 JITter

  JIT编译器并不是把整个程序一次编译完,而是只编译它调用的那部分代码。代码编译过一次后,得到的内部可执行代码就存储起来,直到应用程序终止,编译好的代码就才会丢失。所以,如果再次运行程序,或者启动了程序的两个实例(使用两个不同的进程),JIT编译器都要再次编译IL。一个方法只有在首次调用时才会造成一些性能损耗,之后对该方法的所有调用都以本地代码的形式全速运行。由于编译过程的最后一部分是在运行时进行的,JIT编译器能够确切地知道程序运行在什么类型的处理器上,可以利用该处理器提供的任何特性或特定的机器代码指令来优化最后的可执行代码,以提高性能。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: