在 NetStandard2.0 项目中使用 Roslyn 编译动态创建的代码

Use Roslyn in NetStandard2.0 project to compile dynamically created code

我正在尝试创建一个可重用的 .NET Standard 2.0 库,该库使用 Roslyn 在运行时将代码动态编译为内存程序集。这个动态创建的程序集包含 class 派生自作为库一部分的基础 class 的元素。我通过引用库的应用程序中的反射来实例化它们。项目结构如下所示:

假设我的 netstandard2.0 库中有以下类型:

namespace MyLibrary
{
    public abstract class BaseClass
    {
        public abstract int CalculateSomething();
    }
}

然后我在 .NET Core 2.2 项目中创建以下单元测试:

namespace NetCore2_2.Tests
{
    public static class RoslynTests
    {
        [Fact]
        public static void CompileDynamicallyAndInvoke()
        {
            // Create syntax tree with simple class
            var syntaxTree = CSharpSyntaxTree.ParseText(@"
using System;
using MyLibrary;

namespace Foo
{
    public sealed class Bar : BaseClass
    {
        public override int CalculateSomething()
        {
            return (int) Math.Sqrt(42);
        }
    }
}");
            // Create compilation, include syntax tree and reference to core lib
            var compilation = CSharpCompilation.Create(
                "MyDynamicAssembly.dll",
                new[] { syntaxTree },
                new[]
                {
                    MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
                    MetadataReference.CreateFromFile(typeof(BaseClass).Assembly.Location)
                },
                new CSharpCompilationOptions(
                    OutputKind.DynamicallyLinkedLibrary,
                    optimizationLevel: OptimizationLevel.Release)
            );

            // Compile it to a memory stream
            var memoryStream = new MemoryStream();
            var result = compilation.Emit(memoryStream);

            // If it was not successful, throw an exception to fail the test
            if (!result.Success)
            {
                var stringBuilder = new StringBuilder();
                foreach (var diagnostic in result.Diagnostics)
                {
                    stringBuilder.AppendLine(diagnostic.ToString());
                }

                throw new XunitException(stringBuilder.ToString());
            }

            // Otherwise load the assembly, instantiate the type via reflection and call CalculateSomething
            var dynamicallyCompiledAssembly = Assembly.Load(memoryStream.ToArray());
            var type = dynamicallyCompiledAssembly.GetType("Foo.Bar");
            var instance = (BaseClass) Activator.CreateInstance(type);
            int number = instance.CalculateSomething();
            Assert.Equal((int) Math.Sqrt(42), number);
        }
    }
}

在这个测试中,我首先解析了一段源自netstandard2.0库中BaseClass的C#代码。这段代码还引用了System.Math。然后,我创建了一个 C# 编译对象,其中包含对(.NET Core 2.2 的)核心库和我的库的引用。此编译对象将 DLL 发送到内存流。如果编译失败,测试将失败并出现包含所有诊断信息的异常。

此单元测试失败并显示以下错误消息:

(7,31):错误 CS0012:类型 'Object' 在未引用的程序集中定义。您必须添加对程序集的引用 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.

(11,26):错误 CS0012:类型 'Object' 在未引用的程序集中定义。您必须添加对程序集的引用 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'.

我有以下问题:

  • 它不起作用,因为您的库包含 BaseClass,目标是 .netstandard2.0(这意味着该库引用 netstandard.dll 2.0)并且这假设您的库引用库BaseClass,应该引用 netstandard.dll 2.0 以正确解析所有对应的类型。因此,您应该添加对它们的引用(.net47netstandard.dll.netcore2.2 的类似 .netstandard.dll。 (顺便说一句,当您从 .net47 库中引用 .netstandard2.0 时,您可能应该添加一些额外的库作为对 path_to_visual_studio\MSBuild\Microsoft\Microsoft.NET.Build.Extensions 的引用)
  • Roslyn Compilationtarget framework 一无所知,它也不应该对此一无所知。 Compilation 适用于树和引用(当然还有一些选项和引用的元数据),因此您应该手动附加编译中需要的引用。 (顺便说一句,如果你有一个 csprojsln 文件,你可以使用 MsBuildWorkspace 允许从项目或解决方案文件中获得现成的编译,在大多数情况下)
  • 如果您知道或可以找到编译中需要的所有引用,我建议您手动创建 Compilation,否则尝试使用 Microsoft.CodeAnalysis.Workspaces.MSBuild 来分析 .csproj.sln 文件,然后从中检索 CompilationMicrosoft.Net.Compilers.Toolset 只是让您有可能通过未安装在您的系统上但包含在该软件包中的编译器来编译您的项目。