You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中如何忽略或覆盖错误的程序集绑定重定向?

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 11:43:10