Roslyn编译动态程序集引用:为何运行时仍尝试加载磁盘DLL?
问题分析与解决方案
核心问题根源
你遇到的问题本质是编译时元数据引用与运行时程序集加载上下文不匹配:
- 编译
Actions.cs时,MetadataReference.CreateFromImage仅向Roslyn提供MyClass的类型元数据,确保语法和类型检查通过。生成的Actions程序集会记录它依赖的Types程序集名称(即你用Path.GetRandomFileName()生成的随机名称)。 - 虽然你调用
Assembly.Load(bytes)加载了Types程序集,但该程序集被加载到CLR的无加载上下文区域,默认的程序集解析逻辑不会主动扫描这个区域。当运行时实例化MyClass时,CLR需要加载依赖的Types程序集,会优先去磁盘查找同名文件,找不到就抛出FileNotFoundException。
而仅声明MyClass a;时无需实例化对象,CLR不需要加载Types程序集的实现代码,仅靠编译时的元数据就能运行,因此不会触发异常。
解决方案
方案1:注册程序集解析事件(兼容.NET Framework/.NET Core)
保存加载后的Types程序集引用,注册CLR的程序集解析事件,当CLR找不到依赖程序集时,直接返回已加载的实例。
// 全局保存加载后的Types程序集实例 Assembly typesAssembly = null; // 编译并加载Types.cs using (var ms = new MemoryStream()) { EmitResult result = typesCompilation.Emit(ms); if (!result.Success) { // 处理编译错误 foreach (var diag in result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error)) { Console.WriteLine(diag.ToString()); } return; } ms.Seek(0, SeekOrigin.Begin); var bytes = ms.ToArray(); typesAssembly = Assembly.Load(bytes); References.Add(MetadataReference.CreateFromImage(bytes)); } // .NET Framework 注册AppDomain解析事件 AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => { var targetAssemblyName = new AssemblyName(args.Name); if (targetAssemblyName.Name == typesAssembly.GetName().Name) { return typesAssembly; } return null; }; // .NET Core/5+ 注册AssemblyLoadContext解析事件 AssemblyLoadContext.Default.Resolving += (context, args) => { if (args.Name == typesAssembly.GetName().FullName) { return typesAssembly; } return null; }; // 编译并加载Actions.cs的逻辑不变 string assemblyName = Path.GetRandomFileName(); CSharpCompilation compilation = CSharpCompilation.Create( assemblyName, syntaxTrees: new[] { syntaxTree }, references: References, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); using (var ms = new MemoryStream()) { EmitResult result = compilation.Emit(ms); if (!result.Success) { // 处理编译错误 return; } ms.Seek(0, SeekOrigin.Begin); var actionBytes = ms.ToArray(); var actionsAssembly = Assembly.Load(actionBytes); // 调用脚本中的方法 var type = actionsAssembly.GetType("YourScriptType"); var method = type.GetMethod("YourScriptMethod"); method.Invoke(null, null); }
方案2:使用自定义AssemblyLoadContext(推荐.NET Core/5+)
创建自定义的AssemblyLoadContext,将Types和Actions程序集都加载到这个上下文中,CLR会优先在该上下文内查找依赖程序集,无需依赖磁盘文件。
// 创建自定义可回收的AssemblyLoadContext var dynamicLoadContext = new AssemblyLoadContext("DynamicScriptContext", isCollectible: true); // 编译并加载Types.cs到自定义上下文 Assembly typesAssembly = null; using (var ms = new MemoryStream()) { EmitResult result = typesCompilation.Emit(ms); if (!result.Success) { // 处理编译错误 return; } ms.Seek(0, SeekOrigin.Begin); typesAssembly = dynamicLoadContext.LoadFromStream(ms); References.Add(MetadataReference.CreateFromImage(ms.ToArray())); } // 编译Actions.cs string assemblyName = Path.GetRandomFileName(); CSharpCompilation compilation = CSharpCompilation.Create( assemblyName, syntaxTrees: new[] { syntaxTree }, references: References, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); // 加载Actions.cs到同一个自定义上下文 using (var ms = new MemoryStream()) { EmitResult result = compilation.Emit(ms); if (!result.Success) { // 处理编译错误 return; } ms.Seek(0, SeekOrigin.Begin); var actionsAssembly = dynamicLoadContext.LoadFromStream(ms); // 调用脚本方法 var type = actionsAssembly.GetType("YourScriptType"); var method = type.GetMethod("YourScriptMethod"); method.Invoke(null, null); }
内容的提问来源于stack exchange,提问作者Ronen Ness
相关产品推荐
相关产品推荐

