C#中如何忽略或覆盖错误的程序集绑定重定向?
问题描述
TL;DR
我希望在C#中调用Assembly.Load或等效方法时,忽略配置文件中的所有绑定重定向。
详细背景
在.NET开发中频繁遇到DLL地狱问题,最近一次是项目引用的NuGet包依赖不同版本的System.Collections.Immutable(也曾出现其他类似情况)。常规排查步骤:
- 开启VS自动生成绑定重定向选项,问题依旧;
- 尝试
AppDomain.AssemblyResolve事件处理,但绑定重定向仍生效,导致程序去寻找不存在的版本; - 最终发现是app.config中有硬编码的错误绑定重定向,移除后问题解决。
现在想找一种应急代码方案,在无法修改配置的情况下,强制加载指定版本的程序集,不受错误绑定重定向影响(仅用于调试/应急,不用于生产)。
调试用的AssemblyResolve处理器代码
// 防止栈溢出 static List<string> stuffBeingLoaded = new List<string>(); /// <summary> /// 处理程序集解析失败 /// </summary> static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { try { // 忽略资源文件缺失 if (args.Name.Contains(".resources")) return null; // 检查是否已加载同名程序集 AssemblyName targetAssemblyName = new AssemblyName(args.Name); Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == targetAssemblyName.Name); if (assembly != null) return assembly; // 拼接程序集路径 string filename = new AssemblyName(args.Name).Name + ".dll"; string folder = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().Location).LocalPath) ?? @".\"; string assemblyFilePath = Path.Combine(folder, filename); if (!File.Exists(assemblyFilePath) || stuffBeingLoaded.Contains(assemblyFilePath)) { Console.WriteLine("无法加载 {0}, 请求方: {1}", args.Name, args.RequestingAssembly?.Location ?? "(请求方未知)"); return null; } // 标记正在加载,避免递归调用栈溢出 stuffBeingLoaded.Add(assemblyFilePath); try { // 选项1 - 使用Assembly.LoadFrom //var currentDomainAssemblyResolve = Assembly.LoadFrom(assemblyFilePath); // 选项2 - 使用从磁盘读取的AssemblyName加载 // AssemblyName assemblyToLoad; // try // { // assemblyToLoad = AssemblyName.GetAssemblyName(assemblyFilePath); // } // catch (Exception ex) // { // Console.WriteLine(ex.ToString()); // return null; // } //var currentDomainAssemblyResolve = Assembly.Load(assemblyToLoad); // 选项3 - 读取程序集字节流并加载 Stream assemblyFileStream = File.OpenRead(assemblyFilePath); FileInfo assemblyFileInfo = new FileInfo(assemblyFilePath); byte[] contents = new byte[assemblyFileInfo.Length]; assemblyFileStream.Read(contents, 0, (int)assemblyFileInfo.Length); var currentDomainAssemblyResolve = Assembly.Load(contents); return currentDomainAssemblyResolve; } catch (Exception ex) { Console.WriteLine("解析 {1} 时发生意外错误 {0}: {3}, 请求方: {2}", ex.GetType().Name, assemblyFilePath, args.RequestingAssembly?.Location ?? "(请求方未知)", ex.Message); return null; } finally { stuffBeingLoaded.Remove(assemblyFilePath); } } catch (Exception catchall) { Console.WriteLine("发生未预期异常: {0}", catchall); } return null; }
尝试了三种加载方式,指定加载v1.2.3版本时,程序始终被绑定重定向引导去寻找不存在的v1.2.4版本,导致失败。
解决方案
要完全绕过绑定重定向,核心是绕开CLR的版本解析逻辑,直接加载指定的程序集文件,以下是可行的应急方案:
1. 提前加载目标程序集
绑定重定向仅在CLR尝试解析未加载的程序集时生效。如果在程序启动早期就直接把目标版本的程序集加载到AppDomain中,后续CLR会优先使用已加载的程序集,不会再应用绑定重定向。
示例代码:
// 在Main方法开头提前加载指定版本的程序集 string targetAssemblyPath = @"你的路径\System.Collections.Immutable.1.2.3.dll"; byte[] assemblyBytes = File.ReadAllBytes(targetAssemblyPath); Assembly.Load(assemblyBytes);
这样后续无论绑定重定向指向哪个版本,CLR都会使用已加载的v1.2.3版本。
2. 修改AssemblyResolve逻辑,忽略版本匹配
在AssemblyResolve事件中,直接返回找到的程序集,不验证版本,跳过CLR的版本检查步骤:
修改后的关键片段:
// 跳过版本检查,直接加载指定文件名的程序集 string targetFileName = "System.Collections.Immutable.dll"; string assemblyFilePath = Path.Combine(folder, targetFileName); if (File.Exists(assemblyFilePath)) { byte[] assemblyBytes = File.ReadAllBytes(assemblyFilePath); return Assembly.Load(assemblyBytes); }
注意:这种方式可能导致版本不兼容问题,仅用于应急调试。
3. 创建独立AppDomain实现完全隔离
如果需要彻底隔离绑定重定向的影响,可以创建一个新的AppDomain,在新域中加载程序集——新AppDomain可以指定不加载配置文件,完全规避主域的绑定重定向规则:
示例代码:
// 创建无配置的隔离AppDomain AppDomainSetup setup = new AppDomainSetup { ConfigurationFile = "", // 不加载任何配置文件 ApplicationBase = AppDomain.CurrentDomain.BaseDirectory }; AppDomain isolatedDomain = AppDomain.CreateDomain("IsolatedDomain", null, setup); // 在隔离域中加载目标程序集 byte[] assemblyBytes = File.ReadAllBytes(@"你的路径\System.Collections.Immutable.1.2.3.dll"); Assembly loadedAssembly = isolatedDomain.Load(assemblyBytes); // 通过反射调用程序集功能 Type type = loadedAssembly.GetType("你的命名空间.目标类名"); object instance = Activator.CreateInstance(type); MethodInfo method = type.GetMethod("目标方法名"); method.Invoke(instance, null); // 使用完毕后释放隔离域 AppDomain.Unload(isolatedDomain);
这种方式完全避免了主域的绑定重定向,但实现复杂度较高,适合需要彻底隔离的场景。
关键原理
绑定重定向是CLR在程序集引用解析阶段生效的规则,只要我们在这个阶段之前加载目标程序集,或者绕开CLR的自动解析逻辑,就能规避绑定重定向的影响。Assembly.Load(byte[])是最直接的方式,因为它直接加载内存中的程序集,CLR不会再去匹配版本或应用重定向。
内容的提问来源于stack exchange,提问作者Christine Salter

