我正在尝试使用 Roslyn 在运行时编译 C# 代码。我的编译函数如下:
using System.Collections.Immutable;
using Basic.Reference.Assemblies;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
namespace RuntimeCompiler;
public static class RuntimeCompiler
{
public static void Compile(string sourceCode, string outputPath, string assemblyName="MyAssembly")
{
var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
var compilation = CSharpCompilation.Create(assemblyName)
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location))
.AddSyntaxTrees(syntaxTree);
using var stream = new FileStream(outputPath, FileMode.Create);
var result = compilation.Emit(stream);
if (result.Success) return;
throw new Exception($"Compilation failed");
}
}
我的驱动程序功能如下:
namespace RuntimeCompiler;
public class main
{
public static void Main()
{
const string sourceCode = @"
namespace UtilityLibraries;
public static class StringLibrary
{
public static bool StartsWithUpper(this string? str)
{
if (string.IsNullOrWhiteSpace(str))
return false;
char ch = str[0];
return char.IsUpper(ch);
}
}";
const string outputPath = @"/tmp/mylib3.dll";
RuntimeCompiler.Compile(sourceCode, outputPath, "ass3");
}
}
代码有效,我可以看到我的 dll 正在生成。问题是,当我尝试在单独的 ConsoleApp 中使用 dll 时,如下所示:
using UtilityLibraries;
StringLibrary.StartsWithUpper("asdasd");
编译器抱怨:
类型“Object”是在未引用的程序集中定义的。您必须添加对程序集“System.Private.CoreLib,Version=6.0.0.0,Culture=neutral,PublicKeyToken=7cec85d7bea7798e”的引用。
我还检查了
typeof(object).Assembly.Location
的值,它是/usr/share/dotnet/shared/Microsoft.NETCore.App/6.0.25/System.Private.CoreLib.dll
,所以在我看来,请求的程序集已经添加了。
有什么想法吗?
(如果有人有兴趣仔细观察,我上传了一个最低限度可复制的示例here)
您遇到的问题与您要添加的程序集有关。从此评论@github开始:
此行为是“设计使然”。在 Net Core 上,所有核心类型实际上都位于私有 DLL 中(相信名称是
)。 mscorlib 库在运行时主要是类型转发器的集合。 C# 编译器无法解析这些类型转发器,因此会发出错误,因为它无法找到System.Private.Corlib
。System.Object
这是在 Net Core 上工作时的一个已知问题。运行时和编译时程序集有更严格的分离。不支持尝试使用运行时程序集作为编译引用,并且经常破坏运行时程序集的结构。
然后检查 Runtime-code- Generation-using-Roslyn-compilations-in-.NET-Core-App.md 文档。您实际上正在做的是针对运行时(实现)程序集进行编译,这会破坏添加已编译的 dll 作为对项目的引用。您想根据参考进行编译来做到这一点:
根据参考(合同)程序集进行编译
这就是编译器从 msbuild 调用时所做的事情。您需要决定使用哪些参考程序集(例如
)。一旦您决定,您需要从 nuget 包中获取它们并将它们与您的应用程序一起分发,例如以嵌入资源的形式。然后在您的应用程序中从资源中提取二进制文件并为它们创建元数据引用。netstandard1.5
要了解您需要哪些参考程序集以及从哪里获取它们,您可以创建一个空的 .NET Core 库,将目标框架设置为您需要的目标框架,使用进行构建,并在 msbuild 输出中查找msbuild /v:detailed
调用。命令行将列出 C# 编译器用于构建库的所有引用(查找csc.exe
命令行参数)。/reference
我更改了两件事以使代码正常工作:
var assemblyPath = "C:\\Program Files\\dotnet\\packs\\Microsoft.NETCore.App.Ref\\6.0.25\\ref\\net6.0";
var syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
var compilation = CSharpCompilation.Create(assemblyName)
.WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.AddReferences(MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")))
.AddSyntaxTrees(syntaxTree);
<ItemGroup>
<Reference Include="ass3">
<HintPath>ass3.dll</HintPath>
</Reference>
</ItemGroup>
和
const string outputPath = @"/tmp/ass3.dll";
附注
您当前的方法允许动态加载程序集并通过反射调用方法。例如:
var assembly = Assembly.Load(File.ReadAllBytes(outputPath));
var type = assembly.GetType("UtilityLibraries.StringLibrary");
var methodInfo = type.GetMethod("StartsWithUpper", BindingFlags.Public | BindingFlags.Static);
var result = methodInfo.Invoke(null, new Object[] { null });