解读经典-《C#高级编程》第七版-Chapter1-.Net体系结构-Page13-20

解读经典-《C#高级编程》第七版-Chapter1-.Net体系结构-Page13-20

01

程序集

程序集是包含编译好的、基于.Net Framework的代码逻辑单元。一般来说,在Visual Studio中的一个项目即一个程序集,而一个项目中包含多种不同的代码文件。程序集分为可执行程序集和库程序集,比如一个Winform项目就编译为可执行程序集,而Winform项目所包含的其他库项目则编译为库程序集。可执行程序集包含主程序入口点,而库程序集不包含。

程序集的特点是,它是完全自描述的,这和传统的COM组件非常不同,而自描述的程序集正是要解决传统COM组件的信息分离问题。一个COM组件,往往包含了库文件(dll、ocx等文件),同时COM需要注册到注册表生成GUID以供调用,这便对操作系统有了强依赖,有些情况下还需要读取类型库(lib文件),这样一来一个COM组件被分离成三部分,一致性就存在很大的问题。而程序集自描述的“元数据”存储了程序集本身以及程序集所依赖的库文件的信息,从而能够对一致性进行检查而避免了因为运行了不一致的程序集而导致运行中发生错误。对于程序集的一个应用案例就是,我之前用其他更古老的语言开发时,要做自动升级功能时,只能简单的以文件修改日期的新旧来判断是否应该更新,而类库之间相互依赖关系,那根本就无法知道了,除非发布应用自带一个独立的配置文件来描述这些关系。而对于.Net来说,有了程序集,配置是自带在代码里的,就可以做到非常精细化的升级控制,从而保证程序的正确和安全的更新。

程序集分为私有程序集和共享程序集。

  1. 私有程序集可以理解为“拷贝即可执行的绿色程序包”,它不依赖于注册表,程序集只要位于主执行程序的所在目录或子目录下,即可正确运行。
  2. 共享程序集当然是为了“共享”。比如一个公司发布了多个产品,它们依赖于相同的底层类库,公司发布它的产品时,不想每个产品都发布一份底层类库的拷贝,那可能导致不同拷贝间的不一致以及安装包比较庞大,此时就适合使用共享程序集。当然为了实现共享,程序集就需要放在操作系统的公共空间中(全局程序集缓存GAC),这就可能导致不同公司程序集的名称冲突,因此.Net提供了私钥加密法为共享程序集生成一个能保证唯一性的名称(强名strong name)。

因为程序集中包含了描述自身的元数据,所以可以编程访问这些元数据,这个技术称为“反射”。使用反射可实现业务逻辑的“解耦”,是个比较常用的代码优化手段。但反射也不应该过度使用,我的系统里很少反射,因为必须用反射的地方其实很少。我的编程观念是:业务逻辑应该首选使用“代码”来描述,而不是配置文件,这样形成的代码,是非常清晰容易阅读、理解和维护的。我非常讨厌Java界的那种想用配置描述一切的思路。

02

.Net Framework类

.Net的强大功能,给程序员带来的最大的感受可能是来自于.Net Framework类库。.Net类库非常全面,其实学习.Net,很大的部分就是学习.Net类库。

这里补充下我对.Net类库的使用感受。按照我自己的使用习惯,我自己为项目创建的自有类库非常“瘦”,即非常简单。而我非常不赞赏的是,看到很多初学者,也包括一些.Net高手,都喜欢在.Net类库之上再搞一层自己的“类库”,翻开其代码看看,大部分是将.Net类库的一部分抽取出来,简单封装一下自己用,这个封装可能就是一个两三行代码的方法,就这样封装出大量的类库和方法。其目的很明显:我觉得这个是常用的功能,所以封装一下,以后用它就比较方便。这在Java,C#之前的编程时代,可能是正确的,因为那时候没有这么全面而强大的类库,但对于.Net的使用,我是拒绝这么做的,理由如下:

1)版本的阉割:.Net类库非常广泛,而封装自己的类库是个阉割版本,功能大大弱化。

2)思想的禁锢:有人说,我可以在合适的场景使用我的类库,当我的类库实现不了时,就去找.Net类库的方法。但我可以明确的说,实际操作中这很难做到,原因就是:人的路径依赖。当你长年累月的使用自己的类库后,你必然大概率会忘记.Net类库还有更好的实现方法,而倾向于自己去封装新的方法。这就把自己禁锢在自己的一亩三分地里,8年10年后,你大概率对.Net类库还是不生疏的,只是掌握了一些简单的功能。

而就算你真的每次都记得去.Net类库找方法,长期下来很多场景被应用后,当你自己的类库已经封装了.Net类库10个方法中的9个,那你这样的“二传手”封装并没有起到简化开发的作用,意义又在哪里?

3)代码复杂化:在你的编程思想被禁锢后,代码复杂化随之而来。.Net类库有非常丰富的“重载”方法,即一个对象的方法,针对不同的应用场景会有多达十几种封装方法,在不同的应用场景使用合适的方法,可以写出非常简洁优美的代码。而自己封装的类库,往往只能封装其中的一两种重载方法,结果是,在一种场景中写代码是方便了,而在所有其他场景中写代码又必须自己写补充代码来达到和.Net重载方法同样的功能,代码反而变得臃肿。

所以,我的方法是,在问题的源头上,就避免思想被禁锢的可能,即原则上不封装自己的类库,除非.Net类库就是没相关实现。抱着开放的态度,有问题随时在.Net类库中寻找方法。而我确实发现,即使当解决方案规模达到了近百项目,可能用到的自封装类库也不超过几个封装类以及其中的20个方法。

一个有趣的事实是,.Net类库的大部分,都是使用C#编写的。

03

名称空间

名称空间是避免.Net类名冲突的方式。名称空间是可以嵌套的,比如System空间下包含子空间System.Array。

第一章讲解完毕,下回我们开始讲解第二章:核心C#。

曾经觉得元数据很玄乎,现在看看其实没什么,即基础描述性数据,数据的数据。附篇2篇文章可了解一下元数据。

觉得文章有意义的话,请动动手指,分享给朋友一起来共同学习进步。