如何使用AssemblyLoadContext替代Assembly.LoadFrom以避免程序集文件锁定并加载依赖项
如何使用AssemblyLoadContext替代Assembly.LoadFrom以避免程序集文件锁定并加载依赖项
我完全理解你遇到的困扰——Assembly.LoadFrom会直接锁定原DLL文件,导致后续无法运行或删除,而且xUnit的shadowCopy参数没生效,大概率是因为你提前手动加载了程序集,覆盖了xUnit的影子拷贝逻辑。用AssemblyLoadContext确实是当前.NET(Core/.NET 5+)里替代LoadFrom、实现程序集卸载和避免文件锁定的正确方式,我来一步步教你怎么改:
核心思路
- 用可收集的AssemblyLoadContext(设置
isCollectible: true)来隔离测试程序集,这样用完可以主动卸载,释放资源。 - 通过读取文件流加载替代直接从路径加载,彻底避免锁定原文件(搭配
FileShare.ReadWrite允许其他进程操作文件)。 - 自定义依赖解析逻辑,确保测试DLL的依赖也能正确加载且不被锁定。
修改后的代码示例
public IEnumerator<string> GetEnumerator() { // 创建一个可收集的AssemblyLoadContext,用于隔离测试程序集 var testLoadContext = new AssemblyLoadContext("TestAssemblyLoadContext", isCollectible: true); try { var dllDirectory = Path.GetDirectoryName(_pathToTestsDll); // 1. 读取测试DLL到内存流,避免锁定原文件 using var testDllStream = new FileStream( _pathToTestsDll, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); // 允许其他进程读写原文件 testLoadContext.LoadFromStream(testDllStream); // 2. 自定义依赖解析:从测试DLL所在目录加载依赖 testLoadContext.Resolving += (context, assemblyName) => { var dependencyPath = Path.Combine(dllDirectory, $"{assemblyName.Name}.dll"); if (!File.Exists(dependencyPath)) return null; // 找不到的话交给系统默认解析 using var depStream = new FileStream( dependencyPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); return context.LoadFromStream(depStream); }; // 3. 继续你的xUnit测试发现逻辑 var controller = new XunitFrontController(AppDomainSupport.Denied, _pathToTestsDll); using var visitor = new TestDiscoverySink(); controller.Find(false, visitor, TestFrameworkOptions.ForDiscovery()); visitor.Finished.WaitOne(); var tests = visitor.TestCases.Select(testCase => testCase.DisplayName).ToList(); visitor.Finished.Dispose(); return tests.GetEnumerator(); } finally { // 卸载上下文,触发垃圾回收确保资源释放 testLoadContext.Unload(); GC.Collect(); GC.WaitForPendingFinalizers(); } }
关键细节说明
- 为什么用LoadFromStream?:直接用
LoadFromAssemblyPath还是会短暂锁定文件,而读入内存流后加载,原文件完全不会被锁定,同时FileShare.ReadWrite允许其他进程随时修改或删除它。 - 可收集上下文的限制:如果你的测试程序集包含非托管代码、COM互操作类型或某些特殊.NET特性,可收集上下文可能无法正常卸载,但常规的xUnit测试项目一般不会有这个问题。
- 依赖解析逻辑:默认的
AssemblyLoadContext会从应用基目录和GAC找依赖,但如果测试DLL的依赖和它在同一目录,就需要通过Resolving事件手动加载,避免找不到依赖的问题。 - 为什么之前的shadowCopy没用?:你先调用了
Assembly.LoadFrom直接加载了原文件,导致文件被锁定,xUnit的影子拷贝无法绕过这个锁定——现在去掉Assembly.LoadFrom的调用,改用内存流加载,xUnit的shadowCopy参数也能正常工作(不过我们的方案已经不需要它了)。
备注:内容来源于stack exchange,提问作者Marat Tim
相关产品推荐
相关产品推荐

