.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. 程序集加载优先级
- 检查全局程序集缓存 (
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>
在此配置中,运行时会在 bin
和 lib
子目录中查找程序集。
- 代码基 (
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;
}
}
学习参考链接: