c# - 在 Roslyn 的 .net core 中动态选择引用

标签 c# .net-4.0 roslyn .net-5 csharpcodeprovider

TL;DR

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

上下文

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

    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 无法正确解析,因为我收到错误:

Error CS0518: Predefined type 'System.Object' is not defined or imported

最佳答案

使用 Roslyn 针对 .net5 进行编译时,所面临的挑战与针对旧版 .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));
}

有了这些引用资料

  1. 编译没有错误
  2. 生成的程序集可以在其他项目中使用,而不会提示缺少引用(例如 System.Private.CoreLib.dll)

框架的所有组件?当监视生成的代码时,您将看到仅引用了所需的程序集。

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

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

关于c# - 在 Roslyn 的 .net core 中动态选择引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68109143/

相关文章:

c# - 如何使用带有 ASP.Net/C# 的 MySQL ODBC 将自动增量 ID 插入 MySQL 表中?

c# - 如何在一个 Jenkins 项目中运行多个 NUnit 测试项目?

c# - 加载正在编写独立代码分析器的解决方案

c# - 向 ScriptCS 添加默认的 using 指令

c# - 网络 PC 的 DirectoryInfo 抛出 ArgumentException

c# - Dummies 的控制反转(IoC/依赖注入(inject))

wpf - WPF 中的身份验证和角色

entity-framework - 在 Visual Studio 2010 中生成 edmx 以使用外键列名称时,是否有任何方法可以更改导航属性名称约定?

c# - HttpContext 在 global.asax.cs 中不起作用

c# - 如何使用 Roslyn 在解决方案中查找 MethodDeclarationSyntax 的所有用法