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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 12:10:58