如何动态加载程序集及其依赖项且避免文件锁定?
动态加载程序集及其依赖项,避免程序集被进程锁定
我创建了一个包含三个项目的解决方案,场景如下:
1. Entities项目
包含Person类:
namespace Entities { public class Person { public string Name { get; set; } } }
2. Business项目
引用Entities项目,包含PersonBusiness类:
using Entities; namespace Business { public class PersonBusiness { public string GetPersonName(string name) { return name; } public Person GetPerson(string name) { Person person = new Person(); person.Name = name; return person; } } }
3. Runner项目
通过自定义AssemblyLoadContext动态加载Business.dll,代码如下:
using System.Runtime.Loader; namespace Runner { public class AssemblyLoader : AssemblyLoadContext { internal AssemblyLoader() : base(isCollectible: true) { } } internal class Assembly { private dynamic Instance { get; set; } private AssemblyLoader AssemblyLoader { get; set; } private Type AssemblyType { get; set; } public void Load() { AssemblyLoader = new AssemblyLoader(); using (var fs = new FileStream(@"C:\temp\assemblies\business.dll", FileMode.Open, FileAccess.Read)) { System.Reflection.Assembly assembly = AssemblyLoader.LoadFromStream(fs); AssemblyType = assembly.GetType("Business.PersonBusiness"); Instance = Activator.CreateInstance(AssemblyType); Console.WriteLine(Instance.GetPersonName("Person name")); // 启用下方代码可运行但会锁定程序集 //AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\temp\assemblies\Entities.dll"); Console.WriteLine(Instance.GetPerson("Person name 2").Name); } } public void Unload() { AssemblyLoader.Unload(); } } }
运行时错误
System.IO.FileNotFoundException: 'Could not load file or assembly 'Entities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
尝试用AssemblyLoadContext.Default.LoadFromAssemblyPath加载Entities.dll可正常运行,但会锁定该程序集文件,无法在运行时修改或删除。
解决方案
问题根源是自定义AssemblyLoader未处理依赖项加载逻辑:当Business.dll依赖Entities.dll时,默认会到AssemblyLoadContext.Default中查找,但自定义上下文未加载该依赖,因此抛出找不到的异常;而直接用Default上下文加载会因保持文件句柄导致锁定。
要实现无锁定加载,需让自定义上下文通过文件流加载依赖项,重写AssemblyLoadContext.Load方法即可实现:
1. 修改自定义AssemblyLoader类
using System.Linq; using System.IO; public class AssemblyLoader : AssemblyLoadContext { private readonly string _assemblyDir; internal AssemblyLoader(string assemblyDir) : base(isCollectible: true) { _assemblyDir = assemblyDir; } protected override Assembly? Load(AssemblyName assemblyName) { // 优先检查当前上下文是否已加载该程序集 var loadedAssembly = Assemblies.FirstOrDefault(a => a.GetName().Name == assemblyName.Name); if (loadedAssembly != null) { return loadedAssembly; } // 构建依赖程序集的路径 string assemblyPath = Path.Combine(_assemblyDir, $"{assemblyName.Name}.dll"); if (File.Exists(assemblyPath)) { // 通过文件流加载,避免锁定文件 using var fs = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read); return LoadFromStream(fs); } // 找不到则返回null,让系统尝试其他上下文加载 return null; } }
2. 修改Runner项目的Load方法
public void Load() { string assemblyDir = @"C:\temp\assemblies"; AssemblyLoader = new AssemblyLoader(assemblyDir); string businessDllPath = Path.Combine(assemblyDir, "business.dll"); using (var fs = new FileStream(businessDllPath, FileMode.Open, FileAccess.Read)) { System.Reflection.Assembly assembly = AssemblyLoader.LoadFromStream(fs); AssemblyType = assembly.GetType("Business.PersonBusiness"); Instance = Activator.CreateInstance(AssemblyType); Console.WriteLine(Instance.GetPersonName("Person name")); Console.WriteLine(Instance.GetPerson("Person name 2").Name); } }
核心原理
- 重写
Load方法后,自定义上下文会自行处理依赖项加载,通过文件流读取程序集,不会持有文件句柄,因此不会锁定文件; - 以
isCollectible: true创建的上下文,调用Unload方法后,只要无残留引用,就能释放相关资源,程序集文件可随时修改或删除。
内容的提问来源于stack exchange,提问作者Eric Boeing
相关产品推荐
相关产品推荐

