在 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'.
我有以下问题:
- 这是否不起作用,因为在 .NET Standard 2.0 项目中引用了 Roslyn NuGet 包,因此总是尝试编译为 netstandard2.0 目标框架名字对象? 我怀疑netstandard2.0 有不同的
System.Object
定义,转发给目标平台的实际实现。而我的编译单元中并没有引用这个转发定义
- 有没有办法改变目标框架?我查看了
CSharpCompilationOptions
和EmitOptions
,但找不到任何让我改变的东西目标框架。
- 我是否可能需要使用另一个 Roslyn NuGet 包,例如 Microsoft.Net.Compilers.Toolset? 我尽量避免这种情况,因为实际上我想使用默认编译器而不是那些在 NuGet 包中。
- 它不起作用,因为您的库包含
BaseClass
,目标是 .netstandard2.0
(这意味着该库引用 netstandard.dll 2.0
)并且这假设您的库引用库BaseClass
,应该引用 netstandard.dll 2.0
以正确解析所有对应的类型。因此,您应该添加对它们的引用(.net47
的 netstandard.dll
或 .netcore2.2
的类似 .netstandard.dll
。
(顺便说一句,当您从 .net47
库中引用 .netstandard2.0
时,您可能应该添加一些额外的库作为对 path_to_visual_studio\MSBuild\Microsoft\Microsoft.NET.Build.Extensions
的引用)
- Roslyn
Compilation
对 target framework 一无所知,它也不应该对此一无所知。 Compilation
适用于树和引用(当然还有一些选项和引用的元数据),因此您应该手动附加编译中需要的引用。 (顺便说一句,如果你有一个 csproj
或 sln
文件,你可以使用 MsBuildWorkspace
允许从项目或解决方案文件中获得现成的编译,在大多数情况下)
- 如果您知道或可以找到编译中需要的所有引用,我建议您手动创建
Compilation
,否则尝试使用 Microsoft.CodeAnalysis.Workspaces.MSBuild 来分析 .csproj
或 .sln
文件,然后从中检索 Compilation
。 Microsoft.Net.Compilers.Toolset
只是让您有可能通过未安装在您的系统上但包含在该软件包中的编译器来编译您的项目。
我正在尝试创建一个可重用的 .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'.
我有以下问题:
- 这是否不起作用,因为在 .NET Standard 2.0 项目中引用了 Roslyn NuGet 包,因此总是尝试编译为 netstandard2.0 目标框架名字对象? 我怀疑netstandard2.0 有不同的
System.Object
定义,转发给目标平台的实际实现。而我的编译单元中并没有引用这个转发定义 - 有没有办法改变目标框架?我查看了
CSharpCompilationOptions
和EmitOptions
,但找不到任何让我改变的东西目标框架。 - 我是否可能需要使用另一个 Roslyn NuGet 包,例如 Microsoft.Net.Compilers.Toolset? 我尽量避免这种情况,因为实际上我想使用默认编译器而不是那些在 NuGet 包中。
- 它不起作用,因为您的库包含
BaseClass
,目标是.netstandard2.0
(这意味着该库引用netstandard.dll 2.0
)并且这假设您的库引用库BaseClass
,应该引用netstandard.dll 2.0
以正确解析所有对应的类型。因此,您应该添加对它们的引用(.net47
的netstandard.dll
或.netcore2.2
的类似.netstandard.dll
。 (顺便说一句,当您从.net47
库中引用.netstandard2.0
时,您可能应该添加一些额外的库作为对path_to_visual_studio\MSBuild\Microsoft\Microsoft.NET.Build.Extensions
的引用) - Roslyn
Compilation
对 target framework 一无所知,它也不应该对此一无所知。Compilation
适用于树和引用(当然还有一些选项和引用的元数据),因此您应该手动附加编译中需要的引用。 (顺便说一句,如果你有一个csproj
或sln
文件,你可以使用MsBuildWorkspace
允许从项目或解决方案文件中获得现成的编译,在大多数情况下) - 如果您知道或可以找到编译中需要的所有引用,我建议您手动创建
Compilation
,否则尝试使用 Microsoft.CodeAnalysis.Workspaces.MSBuild 来分析.csproj
或.sln
文件,然后从中检索Compilation
。Microsoft.Net.Compilers.Toolset
只是让您有可能通过未安装在您的系统上但包含在该软件包中的编译器来编译您的项目。