使用 Roslyn引擎动态编译代码

Roslyn引擎自2014年开源至今这么久,一直没怎么了解过,虽然VS2015早就集成了它。
以前老一套的动态编译方法在 .NET Core中似乎不再支持了,很多方法都是未实现的。下面就介绍如何在.NET Core环境中使用Roslyn进行动态编译。话不多说,Talk is cheap, show me the code.
首先是安装nuget包
Install-Package Microsoft.CodeAnalysis.CSharp
接下来是我们需要动态编译和执行的代码:
使用 Roslyn引擎动态编译代码 - 图1
// 表达式树
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@”
using System;

  1. namespace RoslynCompileSample<br /> {<br /> public class Writer<br /> {<br /> public void Write(string message)<br /> {<br /> Console.WriteLine(message);<br /> }<br /> }<br /> }");<br />![](https://cdn.nlark.com/yuque/0/2021/gif/12898916/1620401035757-59aa97ca-36d3-4c00-8d53-4b449fb5ea85.gif#clientId=u2c102cc2-adee-4&from=paste&height=10&id=u9a779dd2&margin=%5Bobject%20Object%5D&originHeight=20&originWidth=20&originalType=url&status=done&style=none&taskId=uc78e9526-8b78-47fc-9d8f-8005cd75ce7&width=10)<br />紧接着是创建编译对象:<br />![](https://cdn.nlark.com/yuque/0/2021/gif/12898916/1620401036774-8841595a-6e53-4dbb-a27f-bf20c42b75f8.gif#clientId=u2c102cc2-adee-4&from=paste&height=10&id=u0a0bef18&margin=%5Bobject%20Object%5D&originHeight=20&originWidth=20&originalType=url&status=done&style=none&taskId=u190f9257-f16a-455b-9b79-aa24c327dcd&width=10)<br />// 随机程序集名称<br />string assemblyName = Path.GetRandomFileName();

// 元数据引用
MetadataReference[] references = new MetadataReference[]
{
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location)
};

// 创建编译对象
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
使用 Roslyn引擎动态编译代码 - 图2
然后是编译:
使用 Roslyn引擎动态编译代码 - 图3
using (var ms = new MemoryStream())
{
// 将编译后的IL代码放入内存中
EmitResult result = compilation.Emit(ms);

// 编译失败,提示
if (!result.Success)
{
IEnumerable failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);

  1. foreach (Diagnostic diagnostic in failures)<br /> {<br /> Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());<br /> }<br /> }<br /> else<br /> {<br /> // 编译成功则从内存中加载程序集<br /> ms.Seek(0, SeekOrigin.Begin);<br /> Assembly assembly = Assembly.Load(ms.ToArray());<br /> }<br />}<br />![](https://cdn.nlark.com/yuque/0/2021/gif/12898916/1620401036145-3fc3cc93-93f0-4a78-9ad4-9cd2beee40f9.gif#clientId=u2c102cc2-adee-4&from=paste&height=10&id=u85ec50d1&margin=%5Bobject%20Object%5D&originHeight=20&originWidth=20&originalType=url&status=done&style=none&taskId=u70884b9f-5404-4d1c-bc8c-67978ee96c6&width=10)<br />最后则是调用:<br />![](https://cdn.nlark.com/yuque/0/2021/gif/12898916/1620401036203-d9fa3cbb-32b3-4f11-bd26-4b8890957645.gif#clientId=u2c102cc2-adee-4&from=paste&height=10&id=u81050a79&margin=%5Bobject%20Object%5D&originHeight=20&originWidth=20&originalType=url&status=done&style=none&taskId=ua7b0f6fe-9360-418f-9ba4-b1e0797f5a3&width=10)<br />// 反射获取程序集中 的类<br />Type type = assembly.GetType("RoslynCompileSample.Writer");

// 创建该类的实例
object obj = Activator.CreateInstance(type);

// 通过反射方式调用类中的方法。(Hello World 便是我们传入方法的参数)
type.InvokeMember(“Write”,
BindingFlags.Default | BindingFlags.InvokeMethod,
null,
obj,
new object[] { “Hello World” });
使用 Roslyn引擎动态编译代码 - 图4
最后咱们可以封装一个方法,以便于调用:
使用 Roslyn引擎动态编译代码 - 图5
///


/// 动态编译
///

/// 需要动态编译的代码
/// 动态生成的程序集
public static Assembly GenerateAssemblyFromCode(string code)
{
Assembly assembly = null;
// 丛代码中转换表达式树
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
// 随机程序集名称
string assemblyName = Path.GetRandomFileName();
// 引用
var references = AppDomain.CurrentDomain.GetAssemblies().Select(x => MetadataReference.CreateFromFile(x.Location));

// 创建编译对象
CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, new[] { syntaxTree }, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

using (var ms = new MemoryStream())
{
// 将编译好的IL代码放入内存流
EmitResult result = compilation.Emit(ms);

// 编译失败,提示
if (!result.Success)
{
IEnumerable failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
foreach (Diagnostic diagnostic in failures)
{
Console.Error.WriteLine(“{0}: {1}”, diagnostic.Id, diagnostic.GetMessage());
}
}
else
{
// 编译成功,从内存中加载编译好的程序集
ms.Seek(0, SeekOrigin.Begin);
assembly = Assembly.Load(ms.ToArray());
}
}
return assembly;
}