AppDomain.AssemblyResolve事件在.NET中提供程序集加载失败时的自定义解析机制,允许开发者通过注册事件处理程序从指定路径、内存或数据库加载程序集,解决因GAC、基目录或探测路径缺失导致的FileNotFoundException,常用于插件架构、版本冲突处理和动态加载场景。

当.NET运行时尝试加载一个程序集,但通过其标准探测机制(如GAC、应用程序基目录或配置的探测路径)无法找到它时,
AppDomain.AssemblyResolve
FileNotFoundException
TypeLoadException
AppDomain.AssemblyResolve
注册事件处理程序:在你的应用程序启动时,通常在
Main
AppDomain.CurrentDomain.AssemblyResolve
using System;
using System.Reflection;
using System.IO;
public class Program
{
[STAThread]
static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
// 尝试加载一个可能找不到的程序集,以触发事件
try
{
// 假设有一个名为 'MyMissingAssembly' 的程序集
// 并且它不在标准的探测路径中
Assembly.Load("MyMissingAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
Console.WriteLine("MyMissingAssembly loaded successfully.");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"Failed to load assembly: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"An unexpected error occurred: {ex.Message}");
}
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
Console.WriteLine($"[AssemblyResolve Event] 尝试解析程序集: {args.Name}");
// 解析程序集名称,获取不带版本、文化和公钥令牌的部分
string assemblyName = new AssemblyName(args.Name).Name;
string assemblyFileName = assemblyName + ".dll";
// 假设你的缺失程序集在应用程序的 'Plugins' 子目录中
string pluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins", assemblyFileName);
if (File.Exists(pluginPath))
{
Console.WriteLine($"[AssemblyResolve Event] 在 '{pluginPath}' 找到了程序集。");
// 使用 Assembly.LoadFrom 而不是 Assembly.Load,因为它接受文件路径
// Assembly.LoadFrom 会加载程序集并将其绑定到加载上下文
return Assembly.LoadFrom(pluginPath);
}
// 如果在这里找不到,返回 null,CLR会继续其默认的失败处理(抛出异常)
Console.WriteLine($"[AssemblyResolve Event] 无法在自定义路径中找到程序集 '{assemblyName}'。");
return null;
}
}实现事件处理逻辑:在
CurrentDomain_AssemblyResolve
ResolveEventArgs
args.Name
Assembly
null
null
FileNotFoundException
常见的应用场景包括:
app.config
bindingRedirects
程序集加载失败,这在.NET开发中可太常见了,几乎每个开发者都遇到过。它就像一个幽灵,在你以为一切都配置妥当的时候突然跳出来。究其原因,无非是CLR(Common Language Runtime)在它预设的几个地方——全局程序集缓存(GAC)、应用程序的基目录、以及
app.config
具体来说,可能的原因有很多:
MyLib, Version=1.0.0.0
MyLib, Version=2.0.0.0
bindingRedirects
BadImageFormatException
app.config
probing
AssemblyResolve
这些问题往往在开发环境中不明显,因为IDE通常会把所有依赖都放在一起。但一到部署,尤其是在复杂的生产环境或客户机器上,这些问题就暴露无遗了。
AssemblyResolve
AppDomain.AssemblyResolve
工作原理:
new SomeClass()
Type.GetType()
Assembly.Load()
FileNotFoundException
AppDomain.CurrentDomain.AssemblyResolve
ResolveEventArgs
args.Name
args.Name
Assembly.LoadFrom()
Assembly.LoadFile()
Assembly.Load()
Assembly
null
FileNotFoundException
注册方式:
注册
AssemblyResolve
ResolveEventHandler
AppDomain.CurrentDomain.AssemblyResolve
using System;
using System.Reflection;
using System.IO; // 假设你需要用到文件操作
public class MyApplication
{
public static void Main(string[] args)
{
// 关键一步:注册事件处理程序
AppDomain.CurrentDomain.AssemblyResolve += MyResolveEventHandler;
// ... 你的应用程序的其他启动逻辑 ...
// 示例:尝试加载一个自定义位置的程序集
try
{
// 假设我们有一个叫做 "CustomComponent.dll" 的程序集
// 并且它不在应用程序的默认探测路径中
Assembly customAssembly = Assembly.Load("CustomComponent, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
Console.WriteLine($"成功加载 CustomComponent: {customAssembly.FullName}");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"无法加载 CustomComponent: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"发生未知错误: {ex.Message}");
}
Console.WriteLine("程序运行结束。");
Console.ReadKey();
}
// 事件处理程序的签名必须是这样
static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
Console.WriteLine($"[MyResolveEventHandler] 正在尝试解析程序集: {args.Name}");
// 从 args.Name 中提取不带版本信息的程序集名称
string assemblyName = new AssemblyName(args.Name).Name;
string assemblyFileName = assemblyName + ".dll";
// 假设你的自定义程序集放在一个名为 "ExtraLibs" 的子目录中
string customPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ExtraLibs", assemblyFileName);
if (File.Exists(customPath))
{
Console.WriteLine($"[MyResolveEventHandler] 在 '{customPath}' 找到了程序集。");
// 注意:这里使用 Assembly.LoadFrom 是一个常见的做法,
// 它会加载程序集并将其绑定到加载上下文,处理依赖项
return Assembly.LoadFrom(customPath);
}
Console.WriteLine($"[MyResolveEventHandler] 无法在自定义路径中找到 '{assemblyName}'。");
// 如果处理程序无法解析,务必返回 null
return null;
}
}注意事项:
AssemblyResolve
AssemblyResolve
Assembly.LoadFile()
Assembly.LoadFrom()
AssemblyResolve
null
null
在
AppDomain.AssemblyResolve
处理程序集版本冲突的最佳实践:
首先要明确一点,
bindingRedirects
app.config
web.config
bindingRedirects
AssemblyResolve
解析请求的程序集名称:
ResolveEventArgs.Name
// 示例:解析请求的程序集名称和版本 AssemblyName requestedAssembly = new AssemblyName(args.Name); string simpleName = requestedAssembly.Name; Version requestedVersion = requestedAssembly.Version;
版本映射策略:
Dictionary<string, string>
加载策略:
Assembly.LoadFrom(string assemblyFile)
AssemblyResolve
Assembly.LoadFile(string path)
AssemblyResolve
LoadFile
Assembly.Load(byte[] rawAssembly)
插件加载的最佳实践:
AssemblyResolve
明确的插件目录结构:为插件定义一个清晰的目录结构,例如
Plugins/MyPluginA/MyPluginA.dll
Plugins/MyPluginA/Dependencies/SharedLib.dll
AssemblyResolve
动态扫描插件目录: 在
AssemblyResolve
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string assemblyName = new AssemblyName(args.Name).Name;
string assemblyFileName = assemblyName + ".dll";
// 假设所有插件都在 'Plugins' 目录下,每个插件一个子目录
string pluginsRoot = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
if (Directory.Exists(pluginsRoot))
{
foreach (string pluginDir in Directory.GetDirectories(pluginsRoot))
{
// 检查插件主目录
string pluginAssemblyPath = Path.Combine(pluginDir, assemblyFileName);
if (File.Exists(pluginAssemblyPath))
{
return Assembly.LoadFrom(pluginAssemblyPath);
}
// 检查插件的 'Dependencies' 或 'Lib' 子目录
string dependencyPath = Path.Combine(pluginDir, "Dependencies", assemblyFileName);
if (File.Exists(dependencyPath))
{
return Assembly.LoadFrom(dependencyPath);
}
}
}
return null;
}使用独立的AppDomain进行插件隔离(高级): 对于更健壮的插件系统,尤其是当你需要卸载插件、或者插件有各自的配置和依赖时,为每个插件创建独立的
AppDomain
AppDomain
AssemblyResolve
AppDomain
影子复制(Shadow Copy): 当插件DLLs被加载后,它们的文件会被锁定。这意味着你无法在应用程序运行时更新或删除插件。通过在插件加载时使用影子复制,CLR会将DLL复制到临时位置,然后从那里加载。这样,原始文件就不会被锁定,允许你在不停止应用程序的情况下更新插件。在
AssemblyResolve
AppDomain
ShadowCopyFiles
缓存已解析的程序集路径:
AssemblyResolve
Assembly
private static ConcurrentDictionary<string, Assembly> _resolvedAssembliesCache = new ConcurrentDictionary<string, Assembly>(); static Assembly CurrentDomain_AssemblyResolve(object
以上就是.NET的AppDomain.AssemblyResolve事件如何解决加载失败?的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号