在 .net core 中为 Roslyn 动态选择引用 [英] Choose references dynamically in .net core for Roslyn

查看:36
本文介绍了在 .net core 中为 Roslyn 动态选择引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

TL;DR

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

How do I let the runtime choose the right assemblies in .NET Core 5 for C# plugins compiled at runtime that involve .NET 4.7.2 code?

背景

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

I have a .NET 4.7.2 app on which certain modules behave differently based on some configurable plugins. I have the following code in a .NET 4.7.2 assembly that compiles C# plugins at runtime.

    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(没有其他项目可以参考的项目之一)开始.我写了以下代码

I'm now trying to upgrade to the code (slowly) to .NET 5.0 and I started with the UnitTests (one of the projects to which no other project has a reference). I've written the following code

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

the assemblies are chosen automatically by name by the runtime. In the new code mscorlib doesn't resolve correctly because I'm get an error:

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

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

推荐答案

在使用 Roslyn 针对 .net5 进行编译时,挑战与针对旧的 .net 框架进行编译有很大不同,因为您必须引用引用程序集 不是实现程序集.许多技巧会让您引用System.Private.CoreLib.dll,即一个实现程序集,从而将您引向糟糕的方向.例如MetadataReference.CreateFromFile(typeof(object).Assembly.Location)

When compiling with Roslyn against .net5, the challenge is quite different from compiling against the legacy .net framework, because you have to reference reference assemblies not implementation assemblies. Many tips will lead you in the bad direction by letting you reference System.Private.CoreLib.dll, that is an implementation assembly. e.g. MetadataReference.CreateFromFile(typeof(object).Assembly.Location)

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

The code below references all (VB excepted) reference assemblies of .net 5

foreach (string dll in Api.GetFiles(@"C:Program FilesdotnetpacksMicrosoft.NETCore.App.Ref5.0.0
ef
et5.0", "*.dll"))
{
    if (!dll.Contains("VisualBasic"))
        references.Add(MetadataReference.CreateFromFile(dll));
}

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

If you use the Windows Forms compatibility pack (net5.0-windows), add these assemblies:

foreach (string dll in Api.GetFiles(@"C:Program FilesdotnetpacksMicrosoft.WindowsDesktop.App.Ref5.0.0
ef
et5.0", "*.dll"))
{
    if (!dll.Contains("VisualBasic") && !dll.Contains("PresentationFramework") && !dll.Contains("ReachFramework"))
        references.Add(MetadataReference.CreateFromFile(dll));
}

通过这些参考

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

框架的所有程序集?窥探生成的代码时,您将看到仅引用了所需的程序集.

All assemblies of the framework? When spying into the generated code, you will see that only the needed assemblies are referenced.

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

If the compilation has to run on machines where the above-mentioned directories don't exist, a possible solution is:

  • 将所有这些参考程序集嵌入为嵌入式资源
  • 使用Assembly.GetExecutingAssembly().GetManifestResourceStream 将这些嵌入的资源读取为stream
  • 用这些流填充byte[]
  • 使用 references.Add(MetadataReference.CreateFromImage(BytesFromResource(dll))); 添加引用
  • embed all these reference assemblies as embedded resources
  • use Assembly.GetExecutingAssembly().GetManifestResourceStream to read these embedded resources as stream
  • fill byte[] with those streams
  • use references.Add(MetadataReference.CreateFromImage(BytesFromResource(dll))); to add references

这篇关于在 .net core 中为 Roslyn 动态选择引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆