.NET6中如何从自定义文件夹加载引用程序集?静态构造方法失效
将大型32位桌面WinForms代码库从.NET Framework4.6.2迁移至.NET6,Visual Studio中运行正常。现有约150个csproj项目,部分DLL为多个可执行文件共享,安装时将共享程序集放在C:\Program Files (x86)\MyProgram\Assemblies文件夹,而非复制到每个程序子目录。
自定义了MyAssemblyResolver,可在应用根目录缺少依赖DLL时从Assemblies文件夹正确加载。在.NET Framework4.6.2中,通过Program类的静态构造函数注册AssemblyResolve事件:
static class Program { private static MyAssemblyResolver _assemblyResolver = new MyAssemblyResolver(); static Program() { AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => _assemblyResolver.AssemblyResolve(args.Name); } // Main() and other code is here }
但在.NET6中,移除bin\Debug中的依赖DLL后,会在静态构造函数执行前抛出System.IO.FileNotFoundException: 'Could not load file or assembly'异常,无法进入静态构造函数。即时窗口中用MyAssemblyResolver加载非必需程序集可正常工作。
项目引用方式差异:
- .NET6中通过共享项目间接引用缺失DLL:
在<ProjectReference Include="OneOfSharedProjects.csproj" />OneOfSharedProjects.csproj中:<ProjectReference Include="MissingDLL.csproj" /> - .NET Framework4.6.2中为直接引用:
<ProjectReference Include="MissingDLL.csproj"> <Project>{12345678-FFBC-419B-AEAB-3A9B1A4D4847}</Project> <Name>MissingDLL</Name> </ProjectReference>
补充条件:
- 库未注册到GAC
- 将
AssemblyResolve注册移至Main()也无效,仍在Main()前抛出异常
需求:在.NET6中从自定义文件夹加载引用库,且避免在配置文件中硬编码路径。
1. 用模块初始化器提前注册解析器
.NET 5+支持模块初始化器,可在程序集加载的极早期执行代码,优先级高于静态构造函数和Main方法,能在依赖项尝试加载前完成事件注册。
修改解析器相关代码,添加模块初始化器:
using System.Runtime.CompilerServices; public static class AssemblyResolverBootstrapper { private static MyAssemblyResolver _resolver = new MyAssemblyResolver(); [ModuleInitializer] public static void InitializeResolver() { // 注册AppDomain级别的解析事件 AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => _resolver.AssemblyResolve(args.Name); // 兼容.NET 6+的AssemblyLoadContext场景 AssemblyLoadContext.Default.Resolving += (context, args) => _resolver.AssemblyResolve(args.Name); } }
2. 修改项目引用的复制行为
对于共享DLL的项目引用,设置Private="False",告诉编译器不要将依赖复制到输出目录,匹配你的部署结构:
在主项目的ProjectReference中添加:
<ProjectReference Include="OneOfSharedProjects.csproj"> <Private>False</Private> </ProjectReference>
在共享项目的ProjectReference中同样设置:
<ProjectReference Include="MissingDLL.csproj"> <Private>False</Private> </ProjectReference>
3. 自定义AssemblyLoadContext接管加载逻辑
如果模块初始化器仍无法覆盖所有场景,可创建自定义加载上下文,接管程序集加载逻辑:
public class SharedAssemblyLoadContext : AssemblyLoadContext { private readonly string _sharedAssembliesDir; public SharedAssemblyLoadContext(string sharedDir) : base(isCollectible: false) { _sharedAssembliesDir = sharedDir; } protected override Assembly Load(AssemblyName assemblyName) { // 先尝试默认上下文加载 try { return Default.LoadFromAssemblyName(assemblyName); } catch { // 默认加载失败,尝试从共享目录加载 string assemblyPath = Path.Combine(_sharedAssembliesDir, $"{assemblyName.Name}.dll"); if (File.Exists(assemblyPath)) { return LoadFromAssemblyPath(assemblyPath); } return null; } } }
然后在模块初始化器中初始化并绑定:
[ModuleInitializer] public static void InitializeLoadContext() { // 运行时解析共享目录路径 string sharedPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "MyProgram", "Assemblies" ); var customContext = new SharedAssemblyLoadContext(sharedPath); AssemblyLoadContext.Default.Resolving += (context, args) => customContext.Load(args); }
关键注意事项
- 模块初始化器必须是静态无参方法,且标记
[ModuleInitializer]特性,所在类需为静态类或包含静态方法。 - 共享路径的解析逻辑需在初始化时正确执行(比如通过环境变量、应用配置或系统路径组合),避免硬编码但保证运行时能准确定位。
- 所有间接引用共享DLL的项目层级,都需设置
Private="False",防止编译时自动复制DLL到输出目录。
内容的提问来源于stack exchange,提问作者Albert Mosiałek

