AppDomain

在传统的 Windows 编程中,一个应用程序对应一个进程,进程之间是天然隔离的,一个进程崩了不会影响另一个。但这种隔离很“重”,创建进程开销大,进程间通信也比较麻烦 。

.NET 引入 AppDomain 就是为了解决这个问题:它允许我们在一个进程内创建多个隔离的运行环境,每个环境就叫一个 AppDomain。这样既享受了隔离带来的安全与稳定,又避免了开启多个进程的巨大开销 。

AppDomain 的三大核心特性

  1. 隔离性 (Isolation):这是它最重要的特性。运行在不同 AppDomain 中的代码,其引用的类型、静态变量、甚至某些配置都是独立的。一个域里的代码抛出未处理异常而崩溃,通常不会影响进程中的其他 AppDomain

  2. 可卸载性 (Unloadable):进程一旦启动,就无法卸载。但 AppDomain 可以。你可以创建一个新的域,在里面加载并执行一些程序集(比如一个插件),当不再需要时,可以完整地卸载掉整个 AppDomain,从而释放它占用的所有资源(包括那些原本无法卸载的程序集)。

  3. 可配置的安全性:你可以为不同的 AppDomain 设置不同的权限集。比如,你可以创建一个“沙箱域”来运行从网络下载的不受信任的插件,限制它不能读写硬盘、不能访问注册表,从而保护主程序的安全 。

AppDomain 的常用成员

AppDomain 类在 System 命名空间下,下面这些属性和方法是你最需要掌握的

✨ 常用属性
属性名 作用说明 使用场景
CurrentDomain 静态属性,获取当前线程正在执行的 AppDomain 对象 。 这是你最常用的入口,比如 AppDomain.CurrentDomain.GetAssemblies()
FriendlyName 获取该 AppDomain 的友好名称,便于识别 。 默认域的友好名通常是程序集文件名(如 MyApp.exe);自定义域的名称由你在创建时指定。
BaseDirectory 获取程序集探测的基目录,通常是应用程序的根目录 。 用于定位和加载与应用程序同目录的程序集。
SetupInformation 获取该域的配置信息(如配置文件路径、影子复制设置等)。 当你需要了解一个 AppDomain 是如何被初始化的时候。
⚙️ 常用方法
方法名 作用说明 使用场景
CreateDomain() 静态方法,创建一个新的应用程序域 。 用于创建插件域、沙箱域等。你可以指定名称、安全性证据、配置信息等。
Unload() 静态方法,卸载指定的应用程序域 。 当不再需要某个域时,调用此方法彻底清理其占用的资源。
GetAssemblies() 获取已加载到此应用程序域中的所有程序集 。 用于反射遍历当前域加载了哪些 DLL,常用于实现插件发现或属性扫描。
CreateInstanceAndUnwrap() 在指定程序集中创建类型实例,并返回一个透明的代理对象 。 这是跨域通信的核心方法。用于在“当前域”中创建并操作“另一个域”里的对象。
Load() 将程序集加载到此应用程序域中 。 手动加载一个程序集到当前域。
DoCallBack() 在另一个应用程序域中执行一个委托指定的代码 。 当你想让目标域直接执行一些简单代码,而不需要创建复杂对象时。
SetData() / GetData() 为应用程序域属性分配/获取值 。 一种简单的跨域共享数据的方式(数据会被序列化传递)。
📢 常用事件
事件名 触发时机 使用场景
AssemblyLoad 当程序集被加载到该域时发生 。 用于监控域中加载了哪些程序集。
AssemblyResolve 当 CLR 解析程序集失败时发生 。 用于实现高级的程序集加载逻辑,例如从特定路径或内存中加载程序集。
UnhandledException 当域中有异常未被捕获时发生 。 这是一个全局的异常捕获点,用于记录日志或执行最后的清理工作。
DomainUnload AppDomain 即将被卸载时发生 。 用于在域卸载前进行一些清理工作,比如关闭文件句柄、断开网络连接等。
ProcessExit 当默认域的父进程退出时发生 。 应用程序关闭前最后的清理机会。

⚠️ 重要提示与现状

  • 程序集无法单独卸载:这是 .NET 的一个基本设计。一旦一个程序集(DLL)被加载到 AppDomain 中,就无法单独卸载它。如果你想释放这个程序集,唯一的办法就是卸载整个 AppDomain

  • 跨域通信有代价:虽然比跨进程通信快,但通过代理跨域调用对象方法,仍然比直接调用有性能损耗,因为涉及参数和返回值的序列化/反序列化(按引用传递的对象除外)。

  • 对象传递方式:在域间传递对象时,如果类型继承自 MarshalByRefObject,则通过代理按引用传递;否则,对象会被按值序列化(即复制一份到目标域)。你的 PluginHost 必须继承 MarshalByRefObject

  • 在 .NET Core 和 .NET 5/6/7/8+ 中的变化AppDomain 的概念仍然存在,且是运行时的基石。但为了跨平台支持和简化,创建和卸载额外的 AppDomain 的功能在新的 .NET Core 版本中受到很大限制,并且不推荐作为跨平台代码隔离的主要手段。在大多数新的 .NET (Core) 应用程序中,你很少需要自己创建额外的 AppDomain。如果你有插件或代码隔离的需求,微软现在更推荐使用 AssemblyLoadContext 作为替代方案,它提供了更现代化和可控的程序集加载与卸载能力 。

代码解释:

AppDomain.CurrentDomain.BaseDirectory + @"\Config\DevConfig.xml"

AppDomain.CurrentDomain.BaseDirectory + @"\Config\DevConfig.xml"

  1. AppDomain.CurrentDomain:获取当前运行环境的引用。

  2. .BaseDirectory:这是核心属性。它返回的是 应用程序基目录

    • 特点:它返回的字符串末尾通常已经带了一个反斜杠 \(但在某些环境或 .NET Core 中可能有微小差异)。

    • 现场优势:无论你是双击运行、还是通过 Windows 服务启动,它始终指向 .exe 所在的文件夹。

  3. + @"\Config\DevConfig.xml"

    • @ 符号:这是逐字字符串标识符。有了它,你就不需要写成 \\Config\\...(双斜杠转义),代码更简洁。

    • 路径拼接:将根目录与具体的配置子目录和文件名组合。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐