.NET 程序集加载优先级

66 阅读3分钟

.NET 程序集加载优先级

1. 项目开发背景

在实际开发过程中,时不时会遇到:未能加载文件或程序接的异常。比如下面这个:

内部异常 1:
FileLoadException: 未能加载文件或程序集“System.Windows.Interactivity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)

一般通过NuGet安装Packages时,在app.config中会默认生成对应的依赖版本重定向配置。比如下面这种:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
	<startup>
		<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
	</startup>
	<runtime>
		<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
			<dependentAssembly>
				<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
				<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
			</dependentAssembly>
			<dependentAssembly>
				<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
				<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
			</dependentAssembly>
			<dependentAssembly>
				<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
				<bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
			</dependentAssembly>
			<dependentAssembly>
				<assemblyIdentity name="Ninject" publicKeyToken="c7192dc5380945e7" culture="neutral" />
				<bindingRedirect oldVersion="0.0.0.0-3.3.6.0" newVersion="3.3.6.0" />
			</dependentAssembly>
			<dependentAssembly>
				<assemblyIdentity name="MWArray" publicKeyToken="e1d84a0da19db86f" culture="neutral" />
				<bindingRedirect oldVersion="0.0.0.0-2.19.1.0" newVersion="2.19.1.0" />
			</dependentAssembly>
			<dependentAssembly>
				<assemblyIdentity name="System.Windows.Interactivity" publicKeyToken="31bf3856ad364e35" culture="neutral" />
				<bindingRedirect oldVersion="0.0.0.0-4.5.0.0" newVersion="4.5.0.0" />
			</dependentAssembly>
		</assemblyBinding>
	</runtime>
</configuration>

这里的<bindingRedirect oldVersion="0.0.0.0-4.5.0.0" newVersion="4.5.0.0" />配置会引导CLR去加载对应版本的程序集。如果缺少类似这样的配置,也可以手动添加进来。(最上面的报错,就是缺少app.config中的版本重定向指向,但本地又存在多个不同版本的System.Windows.Interactivity)。

2. 程序集加载优先级

image.png

  • 检查全局程序集缓存 (GAC)

如果程序集是强命名的,并且配置文件或应用程序上下文没有指定其他位置,则运行时会首先在 GAC 中查找该程序集。GAC 是一个系统范围的存储区域,用于存放共享程序集。

  • 检查应用程序基目录

如果程序集未在 GAC 中找到,运行时会检查应用程序的基目录(即包含可执行文件的目录)。例如,如果应用程序位于 C:\MyApp\,则运行时会在此目录中查找所需的程序集。

  • 检查探测路径 (Probing Path)

如果程序集仍未找到,运行时会根据 配置节中的子目录列表进行搜索。这些子目录可以是应用程序基目录下的特定文件夹。例如:

<configuration>
    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <probing privatePath="bin;lib" />
        </assemblyBinding>
    </runtime>
</configuration>

在此配置中,运行时会在 binlib 子目录中查找程序集。

  • 代码基 (CodeBase)

如果程序集的完全限定路径通过 <codeBase> 元素显式指定,则运行时会直接使用该路径加载程序集。例如:

<configuration>
    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <dependentAssembly>
                <assemblyIdentity name="MyAssembly" publicKeyToken="1234567890abcdef" culture="neutral" />
                <codeBase version="1.0.0.0" href="file:///C:/CustomPath/MyAssembly.dll" />
            </dependentAssembly>
        </assemblyBinding>
    </runtime>
</configuration>
  • 重定向 (Redirect)

如果存在版本重定向规则(通过 <bindingRedirect> 配置),运行时会尝试将请求的程序集版本映射到另一个版本。例如:

<configuration>
    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <dependentAssembly>
                <assemblyIdentity name="MyAssembly" publicKeyToken="1234567890abcdef" culture="neutral" />
                <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0" />
            </dependentAssembly>
        </assemblyBinding>
    </runtime>
</configuration>
  • 自定义加载逻辑

如果上述方法均未找到程序集,可以使用 AppDomain.AssemblyResolve 事件提供自定义加载逻辑。开发者可以在事件处理程序中动态加载程序集。

using System;
using System.IO;
using System.Reflection;

class Program
{
    static void Main(string[] args)
    {
        // Step 1: 订阅 AppDomain.AssemblyResolve 事件
        AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;

        // Step 2: 尝试加载 MyAssembly 中的类型
        try
        {
            // 请求加载 MyAssembly.dll 中的某个类型
            Type myType = Type.GetType("MyNamespace.MyClass, MyAssembly");
            if (myType != null)
            {
                Console.WriteLine("Type loaded successfully.");
                object instance = Activator.CreateInstance(myType);
                myType.InvokeMember("SayHello", BindingFlags.InvokeMethod, null, instance, null);
            }
            else
            {
                Console.WriteLine("Failed to load type.");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }

    // Step 3: 实现 AssemblyResolve 事件处理程序
    private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    {
        Console.WriteLine($"AssemblyResolve triggered for: {args.Name}");

        // 获取请求的程序集名称
        string assemblyName = new AssemblyName(args.Name).Name;

        // 指定自定义路径
        string customPath = Path.Combine(@"C:\CustomPath\", $"{assemblyName}.dll");

        // 检查文件是否存在
        if (File.Exists(customPath))
        {
            Console.WriteLine($"Loading assembly from: {customPath}");
            return Assembly.LoadFrom(customPath);
        }

        // 如果文件不存在,返回 null
        Console.WriteLine("Assembly not found in custom path.");
        return null;
    }
}

学习参考链接:

learn.microsoft.com/zh-cn/dotne…

learn.microsoft.com/zh-cn/dotne…