【问题标题】:Use Roslyn in NetStandard2.0 project to compile dynamically created code在 NetStandard2.0 项目中使用 Roslyn 编译动态创建的代码
【发布时间】:2020-01-21 19:25:12
【问题描述】:

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

假设我的 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:“对象”类型是在未引用的程序集中定义的。您必须添加对程序集 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' 的引用。

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

我有以下问题:

  • 这是否不起作用,因为 Roslyn NuGet 包在 .NET Standard 2.0 项目中被引用,因此总是尝试编译为 netstandard2.0 目标框架名字对象?我怀疑 netstandard2.0 有不同的定义System.Object 转发到目标平台的实际实现。而且我的编译单元没有引用这个转发定义。
  • 有没有办法改变目标框架?我看了CSharpCompilationOptionsEmitOptions,但没有找到让我改变目标框架的任何东西。
  • 我是否可能需要使用另一个 Roslyn NuGet 包,例如 Microsoft.Net.Compilers.Toolset 我尽量避免这种情况,因为实际上我想使用默认编译器而不是 NuGet 包中的编译器。

【问题讨论】:

    标签: c# roslyn .net-standard


    【解决方案1】:
    • 它不起作用,因为包含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 Compilation目标框架一无所知,它也不应该对此一无所知。 Compilation 适用于树和引用(当然还有一些选项和引用的元数据),因此您应该手动附加编译中需要的引用。 (顺便说一句,如果你有一个csprojsln 文件,你可以使用MsBuildWorkspace 允许从项目或解决方案文件中获取准备好的编译,在大多数情况下)
    • 如果您知道或可以找到编译中需要的所有引用,我建议手动创建Compilation,否则尝试使用Microsoft.CodeAnalysis.Workspaces.MSBuild分析.csproj.sln文件,然后检索@987654342 @ 从他们。 Microsoft.Net.Compilers.Toolset 只是让您有可能通过未安装在您的系统上但包含在此包中的编译器来编译您的项目。

    【讨论】:

    • 乔治,非常感谢您的全面回答。还有一个问题:您认为最好不要对库使用 netstandard2.0,而是对 net、nercoreapp 等使用多目标?这是手动解决正确的网络标准 DLL 的正确方法吗?因为在运行时,获取配置实际框架的 csproj 文件并不容易。也许有一种方法可以使用反射来获取这些信息?
    • @feO2x,如果您将使用多目标,它假定您将为您的库有几个不同的编译,.net.netcore 的编译等等。它们会有所不同,因为它们通过针对平台具有不同的引用。因此,如果您有简单的方法为您的库获取此编译,那么它会很容易使用,而不是手动解析.netstandard.dll。我认为避免解决问题的简单方法是为所有项目使用一个目标平台,当然如果你能做到的话。
    猜你喜欢
    • 1970-01-01
    • 2017-08-25
    • 2011-12-12
    • 2021-01-29
    • 2019-06-25
    • 2015-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多