本节书摘来自异步社区《.NET程序员面试秘笈》一书中的第1章,面试题14,作者: 张云翯, 更多章节内容可以访问云栖社区“异步社区”公众号查看。
面试题14 简述程序集和应用程序域
.NET程序员面试秘笈
【考点】.NET程序集的知识,.NET应用程序域的理解,.NET应用程序域与程序集的简单应用。
【出现频率】
【解答】
.NET的程序集用于解决DLL HELL,DLL HELL是指与DLL有关的问题。程序集是自我描述的安装单元,这是一个逻辑单元,而非一个文件。程序集可以是包含元数据的多个文件,也可以是一个DLL或EXE文件。简而言之,程序集是作为整体发布的.NET可执行程序或.NET可执行程序的一部分,包含了程序的文件集或资源文件。
程序集分为私有程序集和共享程序集,私有程序集是创建.NET项目时默认的,也是比较常用的方式。私有程序集以可执行文件或库的形式提供应用程序,库中的代码只服务于这个应用程序。而共享程序集是一个公共库,可服务于系统中所有程序。共享程序集必须安装到.NET的特别目录中,而其他被服务的程序则不需要知道安装的地方。
【分析】
任何.NET程序均由程序集构成,程序集是包含已编译的、面向.NET Framework的代码的逻辑单元。当程序集存储于多个文件当中时,则一定有一个主文件包含程序集主程序的入口,这个包含入口的主文件描述了位于相同程序集的其他文件。程序集包含描述自身的元数据,这种元数据位于程序集清单中,可用于检查版本以及自身的完整性。
说明:
动态程序集位于内存中,而非存储于文件中。
应用程序域是.NET中的程序的“边界”。相对于进程边界,应用程序域边界范围更小。在Windows 7/XP中,进程可以有效保证不同的程序安全地运行,当某个程序出错时并不会影响其他程序。但是进程对多程序运行的系统性能作出了妥协,因为进程之间不允许共享内存。可以使用基于DLL的组件解决这个问题,将所有的DLL组件在一个地址空间中运行,不过当某个组件出错时将会影响其他组件。使用应用程序域可以分离组件,并且不会导致类似于进程的性能问题。
说明:
进程有独立的虚拟内存,以保证进程之间的内存无法互写。
在一个进程内可容纳多个应用程序域,这样,.NET中的应用程序域可使多个应用程序运行于同一个进程。在没有代理的情况下,不同的应用程序域中的实例和静态成员无法共享,这样也保证了安全性。
说明:
程序集的代码只需加载一次,以减少内存消耗。
本例展示了程序集和应用程序域的简单应用。在VS环境(VS2008或VS2010)中创建一个新控制台应用程序,名称为AppA,在Program.cs中编写代码如程序1.16所示。
程序1.16 AppA项目:Program.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace AppDomainTest
{
class Program
{
static void Main(string[] args)
{
//创建AppDomain类型的current,用于引用AppDomain.CurrentDomain
AppDomain current = AppDomain.CurrentDomain;
//输出程序的应用程序域的名称
Console.WriteLine("大家好,我是程序集AppA,我所在的应用程序域是{0}。", current.FriendlyName);
}
}
}
执行本程序,可得到结果如图1.22所示。
可见,当应用程序运行时,默认所在的应用程序域的名称为程序名称。现在AppA项目已经编译了最简单的私有程序集,位于ch01AppAAppAbinDebug目录下。接下来用同样的方法在VS环境中创建新项目,名称为AppB,在其Program.cs中编写代码如程序1.17所示。
程序1.17 AppB项目:Program.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace AppDomainTest
{
class Program
{
static void Main(string[] args)
{
//创建AppDomain类型的one,用于引用AppDomain.CurrentDomain
AppDomain one = AppDomain.CurrentDomain;
//输出one的基目录以及名称、上下文信息
Console.WriteLine("大家好,我是程序集B,我的基目录:{0}\n我所在的应用程序域的名称及上下文策略:{1}", one.BaseDirectory, one.ToString());
//在当前的应用程序域中加载程序集AppA.exe
one.ExecuteAssembly("AppA.exe");
Console.WriteLine("\n上面的AppA程序集和下面的AppA程序集位于不同的应用程序域\n");
//创建AppDomain类型的tow,用于引用AppDomain类创建的新应用程序域,其名称为ROOM-A
AppDomain two = AppDomain.CreateDomain("ROOM-A");
//two应用程序域装载(执行)AppA.exe程序集
two.ExecuteAssembly("AppA.exe");
//输出two的基目录以及名称、上下文信息
Console.WriteLine("AppA程序集的基目录:{0}\nAppA程序集所在的应用程序域的名称及上下文策略:{1}", two.BaseDirectory, two.ToString());
}
}
}
执行程序前,首先需添加程序集引用,在VS中的菜单栏中单击“项目|添加引用”,在添加引用的对话框中单击“浏览”选项,如图1.23所示。
浏览AppA项目的程序集路径,即ch01AppAAppAbinDebug目录,选中AppA.exe,单击“确定”按钮即可。这步的操作实际上把AppA程序集直接复制到AppB项目程序集的相同目录下,即复制到ch01AppBAppBbinDebug目录下。这也充分展示了私有程序集安装的便捷性,现在,AppB应用程序执行时即可将AppA程序集装载到新建的应用程序域了。执行AppB项目的程序,可得到结果如图1.24所示。
如图所示,AppA程序集首先被加载到当前应用程序域,然后被加载到名称为“ROOM-A”的应用程序域中。从任务管理器中观察,AppA程序集并没有创建新的进程。AppB程序集所在的应用程序域被称为进程中的主应用程序域,这是运行时自动创建的。
说明:
实际上AppA程序集已加载到AppB.exe进程中运行,这就达到了多个应用程序在同一个进程中运行的目的。
本节问题相对比较靠近.NET的底层,编程者必须理解程序基本的运行过程,才能写出更高效的程序。从本节的代码中可知,AppDomain类用于创建和中断应用程序域,加载和卸载程序集合类等功能,另外AppDomain类还可以枚举应用程序域中的程序集和线程。由于程序集包含了元数据,其中含有所有定义的类型以及这些类型成员的细节,所以可通过反射技术来获取这些数据。
本节示例主要展示了应用程序域的简单使用,其“边界”作用不同于进程,不同应用程序域中的应用程序有自己独立的内存空间。在默认情况下,这些程序互相隔离,保证程序安全运行。示例中AppB.exe程序运行情况如图1.25所示。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。