这两个概念是DotNet程序员经常见到的,但好多人搞不清它们。虽然MSDN对它们有相关的说明,但并不是那么容易看懂。在这里灰灰虫根据自己的理解用简单易懂的语言解绍一下它们。
程序集(assembly)
“程序集是包含编译好的、面向.NET Framework的代码的逻辑单元”
这句话可以理解如下:
首先:程序集是代码的逻辑单元,是逻辑上的概念。它不完全等价于编译好的DLL物理文件,但一般我们认为静态专用程序集与编译出的DLL文件或EXE文件是一致的。
其次:它是被编译好的。它不是项目名称或命名空间,更不是源代码。
最后:它是是面向.NET Framework的。意味着它虽然被编译了,但仍需要依托于.NET Framework来运行。
一个程序集可以是一个包括元数据的DLL或EXE,也可以由多个文件组成,例如资源文件、元数据、DLL和EXE。
注意:Win32的DLL(动态链接库)和DotNet中的DLL(程序集文件)的扩展名虽然都是DLL,但是二者内部的数据组织是不一样的,所以千万不要把二者混为一谈。
程序集的分类:
静态程序集和动态程序集:
静态程序集:静态程序集包括中间语言元数据(MSIL,类似于Java字节码的东西),以及该程序集的资源(位图、JPEG 文件、资源文件等)。静态程序集存储硬盘文件中。
动态程序集:动态程序集直接从内存运行并且在执行前不存储到磁盘上,如ASP.NET 2.0中的网站程序运行的时候产生的是动态程序集。
专用程序集和共享程序集:
专用程序集一般附带在某些软件上,且只为该软件提供专用的类库,这些库包含的代码只能用于该应用程序。一般专用程序集是与应用程序放在同一目录下的,在部署的时候与应用程序一起复制到目标机器上即可。
共享程序集是其他应用程序可以使用的公共库,一般是安装到系统GAC中。因为其他软件可以访问共享程序集,所以所有共享程序集必须带有强名称(由发行者签名)。
本地应用程序目录可用于存储专用程序集,所以专用程序集一般不会有版本冲突问题。其他应用程序都不会重写私有的程序集。当然,仍可以使用私有程序集的版本号,这样有助于代码的修改与管理,但它不是.NET所必须的。这种专用程序集是大多数程序员所喜欢的,它可以很好地避免DLL Hell的问题。但这种专用程序集只能部署在应用程序所在的目录之中,而无法像以往的Win32 DLL那样放置在System32目录中供多个应用程序共享,但有的时候设计人员仍有共享程序集的需求(把程序集部署到GAC目录中),这样一来又带来了我们似乎又看到DELL HELL又在慢慢地逼近我们,其实DLL共享程序集中根本不可能出现DELL HELL。因为共享程序集并不是简单地把私有程序集复制到GAC所在目录中,开发人员只能使用相关工具把强命名程序集(Strong-name Assembly)放在GAC所在目录中。在GAC中可以允许同名称但是不同版本号的程序集存在,当不同版本的强命名程序集安装在GAC中后,不同的应用程序会根据版本号信息去调用不同的程序集,这样就不会产生DLL程序集冲突的问题了。
几个概念:
DLL Hell(DLL地狱)
在过去Win32程序中,DLL文件可以被安装在System32目录中,以供多个应用程序共享。
但这种方式给我们带来了一个新的问题:DLL文件版本控制的问题。如果有两个软件共享同一个DLL,那如果其中一个软件版本升级后把这个DLL文件更新至一个新的版本,那另一个软件就无法再访问原来的DLL版本了。如果再安装第三个软件,这个软件也用到了这个DLL,但版本比现有的DLL版本还要低的话,那一旦产生DLL覆盖后那原有的软件就有可能不能再用了。这就是通常所说的DLL DELL。
强命名程序集(Strong-name Assembly)
强命名程序集要求设计人员为程序集指定版本号、区域、Private Key等信息,CLR则以此信息来找到正确的程序集。
生成Private Key信息:
sn -k c:\sgkey.snk
设置程序集的信息:
![程序集和应用程序域 - Tony - Go ahead!](http://img.blog.163.com/photo/yMnnXIMjSCU4kiWEeEGOxA==/2265592087545715559.jpg)
其中黄色背景部分已过时了。应当如下设置:
![程序集和应用程序域 - Tony - Go ahead!](http://img.blog.163.com/photo/k_2J5uiPbfPvy6ey1C0KEA==/2265592087545715561.jpg)
GAC(Global Assembly Cache-全局程序集缓存)
GAC可以缓存全局使用的程序集,大多数共享程序集都安装在这个缓存中。它对应至硬盘上的一个目录(C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\GAC),不是所有的程序集文件复制到这个目录中就可以实现程序集共享的。要放在其中的程序集应当是强命名的程序集,GAC和强命名程序集实现的共享程序集才可以摆脱DLL DELL的困扰。
将强命名程序集加载至GAC中:
gacutil -i E:\Proj\ClassLibrary831\bin\Debug\ClassLibrary831.dll
![程序集和应用程序域 - Tony - Go ahead!](http://img.blog.163.com/photo/guc0_SOolbxH-sO0oH6bBA==/2265592087545715562.jpg)
查看GAC中的所有的程序集:
gacutil -l
查看GAC中指定的程序集:
gacutil -l ClassLibrary831
如果您将组成应用程序的某个程序集置于全局程序集缓存中,则将不再能够通过使用 xcopy 命令复制应用程序目录来复制或安装该应用程序。您还必须在全局程序集缓存中移动该程序集。
程序集与命名空间的区别:
一个程序集可以包含个命名空间,一个命名空间也可以跨越多个程序集。
如果说命名空间是类库的逻辑组织形式,那么程序集就是类库的物理组织形式。只有同时指定所在命名空间和所在的程序集,才能完全限定类型。你要使用一个外部的类,必须引用该类所在的程序集,并且导入该类所在的命名空间。
现在大家应当知道我们在使用某个外部的类时,为什么要在项目中“添加引用”相关的DLL文件,而且还要在代码中加入using ......了吧?
程序集与项目的区别:
默认的情况下,项目名称和程序集的名称是一致的,但这并不代表二者是一个东西。
项目是我们开发过程中概念,项目是用来统一管理我们开发过程的源代码等信息,创建不同的程序,我们应当建立不同的项目来管理。
程序集是部署与使用中概念,它是我们程序编译出来的逻辑结果,作为静态程序集它对应着硬盘上的DLL文件。
其实我们可以改变程序集的名称,使它和项目的名称不一致:
![程序集和应用程序域 - Tony - Go ahead!](http://img.blog.163.com/photo/zCgB1-duyOgiKa-p27-9UQ==/2265592087545715564.jpg)
这样编译后,我们发现DLL文件名变为指定的程序集名称了。
(车延禄)
应用程序域(AppDomain):
在操作系统中,程序和程序之间应实现某种形式的隔离,防止程序和程序之间产生不可预知的影响,这种隔离是必须的。在DotNet技术之前,通常采用进程来形成隔离边界。每一个应用程序被加载到单独的进程中,每个进程都有其私有的内存,运行在一个进程中的应用程序不能写入另一个应用程序的内存,您也不能在两个进程间进行直接调用。
在.NET结构中,应用程序有一个新的边界:应用程序域。应用程序域是一个用于隔离应用程序的“轻量级分界线”。所谓的“轻量级”是因为多个应用程序域可以存在于单个操作系统进程中。
应用程序域可以确保在一个域中运行的代码不会影响进程中的其他应用程序;能够在不停止整个进程的情况下停止单个应用程序。
进程2321有两个应用程序域。在应用程序域A中,实例化了对象1和2,对象1在程序集1中,对象2在程序集2中。在进程2322中,第二个应用程序域有实例1。实例和静态成员不能在应用程序域之间共享,也不能直接访问另一个应用程序域中的对象。
AppDomain类用于创建和中断应用程序域,加载和卸载程序集。
常用属性和方法:
FriendlyName 取得应用程序域的友好名称
CreateDomain() 创建新的应用程序域。
ExecuteAssembly() 执行应用程序域中的程序集。它可用来执行另一个应用程序域中的代码。
Unload() 执行域的正常卸载。只有应用程序域中正在运行的所有线程都已停止或域中不再有运行的线程之后,才卸载该应用程序域。
创建应用程序域 :
class AppDomain1
{
public static void Main()
{
Console.WriteLine("Creating new AppDomain.");
//创建应用程序域
AppDomain domain = AppDomain.CreateDomain("MyDomain");
//显示当前应用程序域的友好名称
Console.WriteLine("Host domain: " + AppDomain.CurrentDomain.FriendlyName);
//显示新建应用程序域的友好名称
Console.WriteLine("child domain: " + domain.FriendlyName);
}
}
卸载应用程序域:
class AppDomain2
{
public static void Main()
{
Console.WriteLine("Creating new AppDomain.");
//创建应用程序域
AppDomain domain = AppDomain.CreateDomain("MyDomain", null);
Console.WriteLine("Host domain: " + AppDomain.CurrentDomain.FriendlyName);
Console.WriteLine("child domain: " + domain.FriendlyName);
// 卸载应用程序域
AppDomain.Unload(domain);
Console.WriteLine("Host domain: " + AppDomain.CurrentDomain.FriendlyName);
//下面这行代码会产生异常,因为domain对象被卸载了
Console.WriteLine("child domain: " + domain.FriendlyName);
}
}
将程序集加载到应用程序域中
class AppDomain3
{
public static void Main(string[] args)
{
//取得当前应用程序域
AppDomain ad = AppDomain.CurrentDomain;
//加载ClassLibrary831应用程序集
Assembly a = ad.Load("ClassLibrary831");
//取得应用程序集中的NewClass类型
Type t = a.GetType("ClassLibrary831.NewClass");
//创建NewClass类型的实例
object o = Activator.CreateInstance(t);
//给对象的属性赋值
PropertyInfo p1 = t.GetProperty("MyName");
PropertyInfo p2 = t.GetProperty("MyInfo");
p1.SetValue(o, "中国", null);
p2.SetValue(o,"China",null);
//调用对象的show方法
MethodInfo mi = t.GetMethod("show");
mi.Invoke(o, null);
}
}
创建一个新的应用程序域,并加载执行程序集
class Test
{
static void Main(string[] args)
{
AppDomain currentDomain = AppDomain.CurrentDomain;
Console.WriteLine(currentDomain.FriendlyName);
//创建新的应用程序域
AppDomain secondDomain = AppDomain.CreateDomain("New AppDomain");
//在新的应用程序域中加载执行AssemblyA.exe程序集
secondDomain.ExecuteAssembly("AssemblyA.exe");
}
}