Roslyn是什么
从C# 6 开始,编译器被完全用C#重写并且模块化,这个模块化的编译器就是Roslyn。利用Roslyn,我们可以方便地在我们的程序中动态编译代码,即,把代码当做数据传递给Roslyn编译器,得到编译后的程序集。
Roslyn如何使用
step 1.安装并引用Roslyn
在开始使用之前,先在使用Nuget安装Microsoft.CodeAnalysis.CSharp
step 2:在命名空间声明中,添加
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
了解编译流程
在使用Roslyn之前,对编译器如何把源代码翻译成MSIL的过程,即,编译流程有个大概了解,对我们得心应手使用这个工具非常有帮助。
Step 3:源文件读取
编译的第一步是读取源文件,在Roslyn里面用SourceText表示源文件**,支持多种不同的源文件读取,包括从文件流读取、直接在源代码指定literal的方式,像以下两种方式常用:
SourceText st; //从文件流读取
using (var fs = new FileStream(filePath, FileMode.Open))
{
st = SourceText.From(fs);
}
st = SourceText.From(@"public class TestClass {}"); //直接用literal方式指定源代码
Step 4: 语法树解析(根据源文件解析生成语法树)
读取源文件之后,我们需要根据源文件解析生成语法树。在Roslyn里面用SyntaxTree表达语法树。Roslyn在这一步中将会检查语法的合理性,如果有语法错误,可以通过调用SyntaxTree的方法GetDiagnostics查看。在生成语法树的时候,可以通过传入的ParseOption控制编译语言版本设定、预处理变量设定,等。
st = SourceText.From(@"public class TestRoslyn { public void Test(){ Console.FakeMethod(); } }"); //没有引用Console,同时Console里面没有FakeMethod这个方法,但是语法解析不会报错,只要语法是正确的就没问题。
CSharpParseOptions option = new CSharpParseOptions(LanguageVersion.CSharp8, preprocessorSymbols: new List<string>() { "Debug"}); //确定使用C# 的版本和传入的预编译量
var tree = CSharpSyntaxTree.ParseText(st, option);
Debug.Assert(tree.GetDiagnostics().ToList().Count == 0);
Step 5:语义解析
语法解析确保了我们的源代码符合C#的语法,比如,有相对应的{},每个字句后面有;,等。但是语法正确只是第一步,我们需要进一步确保我们每条调用语句都是正确的,即,我们引用的类,已经正确的引用到了我们的编译中并且声明在命名空间引用中,同时我们每个语句调用必须调用在正确的函数上,必须传递正确的参数。要做到这一步,我们需要的是语义解析。
在Roslyn使用CSharpCompilation完成语义解析,这是一个静态类,在创建的时候我们可以传入的参数有很多,但最主要的就是,想要创建的模块名称、参与语义解析的语法树(可以有多个语法树,代表可以编入同一个模块的不同文件)、引用的程序集(mscorlib.dll是必须的)和目标类型(dll或者exe)。
var compileOption = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
var compilation = CSharpCompilation.Create(
"class1",
new List<SyntaxTree> { tree }, //这是前面生成的语法树
new List<MetadataReference>() { MetadataReference.CreateFromFile(typeof(int).Assembly.Location) }, //这是一个得到msconlib的好办法
compileOption);
Debug.Assert(tree.GetDiagnostics().ToList().Count == 0); //如果之前有引用缺失、调用方法错误,这里就会体现出来
Step 7:生成dll,生成IL代码到程序集文件
最后我们把代码发射到程序集文件(可以是exe也可以是dll),同时可以生成pdb文件。
var assemblyPath = @"D:\Roslyn\class1.dll";
var pdbPath = @"D:\Roslyn\class1.pdb";
var result = compilation.Emit(assemblyPath, pdbPath); //result表明了执行是否成功
至此,一个动态编译代码生成程序集的流程就完成了。
生成了dll之后,我们可以通过反射加载,或者直接在工程文件中引用的方式使用。
我们一般在构建脚本系统的时候会较多的使用动态代码编译的方法,通过监听脚本源文件,动态的生成程序集,给脚本系统添加不同的功能。
other
使用CSharpCompilation实现动态编译
// 添加要引用的程序集
List<MetadataReference> refs = new List<MetadataReference>() {
MetadataReference.CreateFromFile (typeof (object).Assembly.Location),
MetadataReference.CreateFromFile (typeof (List<int>).Assembly.Location),
MetadataReference.CreateFromFile (typeof (ASCIIEncoding).Assembly.Location),
MetadataReference.CreateFromFile (typeof (JsonConvert).Assembly.Location),
MetadataReference.CreateFromFile (typeof (IEManageSystemCMSModule).Assembly.Location),
MetadataReference.CreateFromFile (typeof (Entity).Assembly.Location),
};
// 生成C#编译
var cSharpCompilation = CSharpCompilation
.Create(Guid.NewGuid().ToString() + ".dll")
.WithOptions(new CSharpCompilationOptions(
Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary,
usings: null,
optimizationLevel: OptimizationLevel.Debug, // TODO
checkOverflow: false, // TODO
allowUnsafe: true, // TODO
platform: Platform.AnyCpu,
warningLevel: 4,
xmlReferenceResolver: null
))
.AddReferences(refs);
MemoryStream ms = new MemoryStream();
// 要编译的源码
string code = $@"
using System;
using System.Collections.Generic;
using System.Text;
public class ClassA {{
}}
"
cSharpCompilation
// 添加语法树
.AddSyntaxTrees(CSharpSyntaxTree.ParseText(code))
// 生成程序集流
// 这里将流导入到内存流中
// 如果要生成dll文件,使用 Emit(dllPath)
.Emit(ms);
使用 Roslyn引擎动态编译代码
https://www.cnblogs.com/WinHEC/p/Roslyn_Dynamic_Compiling.html
https://www.cnblogs.com/weihanli/p/dynamic-compile-via-roslyn.html
https://blog.csdn.net/Crazy2910/article/details/106918516
如何实现运行时动态定义Controller类型?