Effective C#: 1.正确区分类集、模块和名称空间
陈铭 Microsoft C#/.NET Asia MVP
难度:4/10
.NET是面向网络应用和服务的开发平台,那么就让我们从一个简单的网络应用开始吧——虽然这并不是本章节要讨论的主题。
//hello.cs: Say Hello to the Internet
using System;
using System.Net;
namespace Effective.Csharp.Chapter1 {
public class HelloInternet {
public static void Main() {
string address = "http://www.google.com/search?q=";
string hello = "Hello, Internet";
WebRequest myReq = WebRequest.Create(address + hello);
WebResponse myResponse = myReq.GetResponse();
myResponse.Close();
}
}
}
在向互联网问好之前,我们先得编译我们的程序,因为程序里引用了System.Net,所以编译命令似乎应该这样:
csc /r:System.Net.Dll hello.cs
但是编译器很快就抱怨起来:
error CS0006: Metadata file 'System.Net.Dll' could not be found
看来编译器没有找到System.Net.Dll文件,但怎么会呢?类库参考里分明写着WebRequest和WebResponse是在System.Net名称空间里的啊!在解决这个问题之前,我们不妨先看看下面几个概念的定义:
名称空间(Namespace):C++和Java程序员对名称空间的概念应该并不陌生,名称空间用于在逻辑上将一组功能相关的类型组织在一起,从而避免简单类型命名引起的冲突。比如,一个生物学的类库中可能会包含“树”的类行定义,而描述计算机数据结构的应用中同样需要对“树”结构进行定义。在一个没有名称空间概念的系统中,由于命名的冲突,我们将不能在同一个应用中同时使用上述两个类型库。而在.NET中利用名称空间解决这个问题轻而易举:
namespace Biology {
public class Tree {
//生物学中对“树”的描述
}
}
namespace DataStructure {
public class Tree {
//计算机数据结构中对“树”的描述
}
}
这样,只要在程序中使用“名称空间.类型名”的方式引用类型,就可以完全避免歧义:
//生物学“树”的实例
Biology.Tree tree1 = new Biology.Tree(“Pine”);
//数据结构的“树”实例
DataStructure.Tree tree2 = new DataStructure.Tree();
之所以说名称空间只是在“逻辑上”组织类型,是因为它仅仅在设计开发阶段供程序员和编译器将不同的类型区分开来。运行系统并不理会名称空间的存在,而只是简单的把名称空间看成类型名的一个部分。系统中没有与名称空间对应的目录或文件,同一个名称空间下的类型编译之后也不一定会在物理上存储在一起。
C#允许使用using语句简化名称空间的引用,using 在功能上更接近于C++的using namespace语句,而不是Java的import。它仅仅为程序员在代码中引用类型提供一种简略形式,而不是为编译器提供任何类型信息的实际存储位置,比如:
System.Console.WriteLine(“Hello”);
在没有命名冲突的情况下完全等价于:
using System;
…
Console.WriteLine(“Hello”);
即使是存在命名冲突的情况下,使用using语句也可以极大的简化冗长的名称空间的引用。例如,在System.Windows.Forms和System.Web.UI.Controls两个名称空间中都存在名称为Button的类,如果在同一个项目中我们必须同时使用这两个类(事实上通常情况下很少会在同时引用这两个类,这里仅为说明问题),可以这样写:
using WinForm=System.Windows.Forms;
using WebForm=System.Web.UI.Controls;
WinForm.Button winbtn = new WinForm.Button();
WebForm.Button webbtn = new WebForm.Button();
那么既然名称空间不能提供实际类型信息存储的位置,编译器是如何取得这些类型信息从而完成编译的呢?.NET又是如何在物理上把各种类型组织在一起的呢?为此,.NET引进了类集(Assembly)概念。
类集(Assembly):类集是.NET中相关类型的物理组织形式。它是进行应用程序部署、版本控制、重用和权限分配的基本单位。类集包含了类集清单(Manifest)、在类集中定义的类型信息(Metadata)、类方法的实现代码以及其他资源。类集可以由一个或多个文件组成,这些文件或者包含资源数据,或者是包含类型信息和实现的PE格式文件。
包含了类型信息和实现的PE格式文件称为一个模块(Module),模块是运行时类型加载的基本单位。如果程序引用了模块中的某个类,那么CLR将会将整个模块加载到内存中。关于如何有效分割模块提高程序性能的更多信息,请参考条款X。
类集清单(Manifest)包含了类集层次上的信息元数据,例如类集的名称、版本以及它所包含的文件等。类集清单必须完整的保存在类集的一个模块当中,如下图所示:
在编译C#程序的时候,编译器要求必须指明在哪些类集中查找当前程序引用的类型,例如:System.Console类的实现包含在mscorlib类集中,那么编译一个引用了System.Console的程序的编译命令为:
csc /r:mscorlib.dll myprog.cs
其中,/r:引用的mscorlib.dll是mscorlib类集中包含类集清单的模块。(事实上对于mscorlib类集并不需要特别引用,稍候将会介绍)
类集与名称空间的关系
.NET文档中对两者关系的描述是:“名称空间在概念上与类集成正交关系”。用更通俗的话说,它们之间没有任何直接联系。一个名称空间中的类可能分布在几个类集当中,一个类集也可能包含几个不同的名称空间中类的实现。系统类库中确实存在很多同名的类集和名称空间,但那只能说是命名上的巧合罢了。
下表列出了常用的名称空间以及它们的实现类集之间的对应关系:
名称空间
对应的类集
System
mscorlib, System
System.IO
mscorlib, System
System.Xml
System.Data, System.Xml
System.Data
System.Data
System.Net
System
System.Reflection
mscorlib,
System.Security
mscorlib,
System.InteropServices
mscorlib,
System.Runtime.Remoting
mscorlib,
System.Runtime.Serialization
mscorlib,
这里我们看到System.Net名称空间的实现包含在System类集中,所以要正确编译本章开始提及的程序,我们必须在编译命令中引用System类集而不是System.Net:
csc /r:System.Dll hello.cs
程序顺利编译通过。
TIP:在.NET Framework文档中,我们可以轻松的找到具体类型所属的名称空间、类集。例如,文档Console类的概述部分包含了如下信息:
public sealed class Console
…
Requirements
Namespace: System ß 类型所属名称空间
Platforms: Windows 98, Windows NT 4.0, Windows Millennium Edition, Windows 2000, Windows XP Home Edition, Windows XP Professional, Windows .NET Server family
Assembly: Mscorlib (in Mscorlib.dll) ß 所属类集
似乎故事至此应该告一段落了,但是——稍等片刻:既然我们必须向编译器指出程序中需要引用的类集,为什么编译一个引用了System.Console类的程序并不真的需要加上/r:mscorlib.dll?为什么有时候引用了System.Xml名称空间的类型却并不需要加上/r:System.Data.Dll或者/r:System.Xml.Dll呢?
这里有两种不同的情形:mscorlib类集在.NET类库中的地位非常特殊,所有的内建类型都定义在这个类集中,而且.NET类继承体系的根类Sytem.Object也定义在这个类集当中。几乎所有的应用程序都必须引用这个类集,所以C#编译器在编译过程中会自动加上对mscorlib类集的引用。
另外,C#编译器允许使用配置文件来简化编译参数的设置,例如将如下内容保存到C#配置文件MyExe.rsp:
#注释:编译source1.cs source2.cs,生成MyExe.exe
/target:exe /out:MyExe.exe source1.cs source2.cs
然后就可以通过制定配置文件的方式来编译MyExe程序了:
csc @MyExe.rsp
需要引用的类集也是配置文件的内容之一,可以在配置文件中指定程序需要引用得类集。鉴于一些类集在编写程序的过程中比较常用,C#的设计者将这些类集的引用放在了C#编译器的缺省配置文件当中,该配置文件位于%SystemRoot%\Microsoft.Net\Framework\%version%\csc.rsp,其中%SystemRoot%是Windows的安装目录,而%version%是.NET Framework的版本号。该文件的内容如下:
# This file contains command-line options that the C#
# command line compiler (CSC) will process as part
# of every compilation, unless the "/noconfig" option
# is specified.
# Reference the common Framework libraries
/r:Accessibility.dll
/r:Microsoft.Vsa.dll
/r:System.Configuration.Install.dll
/r:System.Data.dll
/r:System.Design.dll
/r:System.DirectoryServices.dll
/r:System.dll
/r:System.Drawing.Design.dll
/r:System.Drawing.dll
/r:System.EnterpriseServices.dll
/r:System.Management.dll
/r:System.Messaging.dll
/r:System.Runtime.Remoting.dll
/r:System.Runtime.Serialization.Formatters.Soap.dll
/r:System.Security.dll
/r:System.ServiceProcess.dll
/r:System.Web.dll
/r:System.Web.RegularExpressions.dll
/r:System.Web.Services.dll
/r:System.Windows.Forms.Dll
/r:System.XML.dll
这样,C#编译器在编译任何程序的时候,都会自动引用以上列出的所有类集——除非通过使用/noconfig显式指定忽略配置文件。(完)
* 本文系原创作品,未经作者许可请勿转载。