在 .net 核心中为 Roslyn 动态选择引用

Posted

技术标签:

【中文标题】在 .net 核心中为 Roslyn 动态选择引用【英文标题】:Choose references dynamically in .net core for Roslyn 【发布时间】:2021-09-07 13:23:57 【问题描述】:

TL;DR

如何让运行时在 .NET Core 5 中为在运行时编译的涉及 .NET 4.7.2 代码的 C# 插件选择正确的程序集?

上下文

我有一个 .NET 4.7.2 应用程序,根据某些可配置的插件,某些模块的行为会有所不同。我在运行时编译 C# 插件的 .NET 4.7.2 程序集中有以下代码。

    public OperationResult<Assembly> CompileClass(string code, string[] references, string fileName, bool generateInMemory = true, bool includeDebugInformation = true)
    
        OperationResult<Assembly> result = new OperationResult<Assembly>  Success = true ;

        try
        
            string pluginsFolder = Path.Combine(InsproConfiguration.GetSettings().PluginsFolder);
            bool keepSoureceFilesAfterCompiling = false;
#if (DEBUG)
            keepSoureceFilesAfterCompiling = true;
#endif

            if (!Directory.Exists(pluginsFolder)) 
            
                Directory.CreateDirectory(pluginsFolder); 
            

            using (CSharpCodeProvider compiler = new CSharpCodeProvider(new Dictionary<string, string>   "CompilerVersion", "v4.0"  ))
            
                CompilerParameters parameters = new CompilerParameters()
                
                    GenerateInMemory = generateInMemory,
                    IncludeDebugInformation = includeDebugInformation,
                    OutputAssembly = Path.Combine(pluginsFolder, fileName) + ".dll",
                    CompilerOptions = "/debug:full",
                    TempFiles = new TempFileCollection  KeepFiles = keepSoureceFilesAfterCompiling 
                ;
                parameters.ReferencedAssemblies.AddRange(references);
                CompilerResults compiledCode = compiler.CompileAssemblyFromSource(parameters, code);
                var errors = new StringBuilder();

                foreach (CompilerError error in compiledCode.Errors)
                
                    errors.AppendLine($"Error in line error.Line, Column error.Column: error.ErrorText");
                

                if (!string.IsNullOrEmpty(errors.ToString()))
                
                    result.HandleFailure(errors.ToString());
                

                result.ResultObject = compiledCode.CompiledAssembly;
            
        
        catch (Exception ex)
        
            LogService.Current.LogError(ex);
        

        return result;
    

我现在正在尝试将代码(慢慢地)升级到 .NET 5.0,并从 UnitTests(其他项目没有参考的项目之一)开始。我写了以下代码

public OperationResult<Assembly> CompileClassWithRoslyn(string code, List<string> referenceAssemblies, string assemblyName)

        OperationResult<Assembly> result = new OperationResult<Assembly>();

        try
        
            //Set file name, location and referenced assemblies
            string pluginsFolder = Path.Combine(InsproConfiguration.GetSettings().PluginsFolder);
            SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
            var trustedAssembliesPaths = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")).Split(Path.PathSeparator);

            if (!referenceAssemblies.Any(a => a.Contains("mscorlib"))) 
            
                 referenceAssemblies.Add("mscorlib.dll"); 
            

            var references = trustedAssembliesPaths.Where(p => referenceAssemblies.Contains(Path.GetFileName(p)))
                                                        .Select(p => MetadataReference.CreateFromFile(p))
                                                        .ToList();

            CSharpCompilation compilation = CSharpCompilation.Create(
                assemblyName,
                syntaxTrees: new[]  syntaxTree ,
                references: references,
                options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

            using (var ms = new MemoryStream())
            
                EmitResult emitResult = compilation.Emit(ms);

                if (!emitResult.Success)
                
                    IEnumerable<Diagnostic> failures = emitResult.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error);
                    result.HandleFailure(failures.Select(f => f.GetMessage()));
                
                else
                
                    ms.Seek(0, SeekOrigin.Begin);
                    Assembly assembly = Assembly.Load(ms.ToArray());                     
                
            
        
        catch (Exception ex)
        
            return result.HandleFailure(ex);
        

        return result;
    

在旧代码中,在

parameters.ReferencedAssemblies.AddRange(references);
CompilerResults compiledCode = compiler.CompileAssemblyFromSource(parameters, code);

程序集由运行时按名称自动选择。在新代码中,mscorlib 无法正确解析,因为出现错误:

错误 CS0518:未定义或导入预定义类型“System.Object”

【问题讨论】:

【参考方案1】:

在针对 .net5 使用 Roslyn 进行编译时,挑战与针对旧版 .net 框架进行编译完全不同,因为您必须引用引用程序集而不是实现程序集。通过让您引用 System.Private.CoreLib.dll(这是一个实现程序集),许多提示会引导您走向错误的方向。例如MetadataReference.CreateFromFile(typeof(object).Assembly.Location)

下面的代码引用了 .net 5 的所有(VB 除外)引用程序集

foreach (string dll in Api.GetFiles(@"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\5.0.0\ref\net5.0", "*.dll"))

    if (!dll.Contains("VisualBasic"))
        references.Add(MetadataReference.CreateFromFile(dll));

如果您使用 Windows 窗体兼容包 (net5.0-windows),请添加以下程序集:

foreach (string dll in Api.GetFiles(@"C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\5.0.0\ref\net5.0\", "*.dll"))

    if (!dll.Contains("VisualBasic") && !dll.Contains("PresentationFramework") && !dll.Contains("ReachFramework"))
        references.Add(MetadataReference.CreateFromFile(dll));

有了这些参考资料

    编译没有错误 生成的程序集可用于其他项目,而不会抱怨缺少引用(例如 System.Private.CoreLib.dll)

框架的所有组件?窥探生成的代码时,您会看到只引用了所需的程序集。

如果编译必须在上述目录不存在的机器上运行,可能的解决方案是:

将所有这些参考程序集嵌入为嵌入资源 使用Assembly.GetExecutingAssembly().GetManifestResourceStream 将这些嵌入资源读取为stream 用这些信息流填充byte[] 使用references.Add(MetadataReference.CreateFromImage(BytesFromResource(dll))); 添加引用

【讨论】:

以上是关于在 .net 核心中为 Roslyn 动态选择引用的主要内容,如果未能解决你的问题,请参考以下文章

基于 Roslyn 实现代码动态编译

如何在 ASP.NET MVC 4 中为 CSP 使用动态随机数

Roslyn导致发布网站时报错:编译失败

使用 Roslyn 代码分析在引用的程序集中查找符号

在 Roslyn 分析器/源生成器中,有啥方法可以区分直接和传递程序集引用

Roslyn 如何获得一个类的引用