受欢迎的博客标签

Roslyn-How to use Roslyn Step by step?

Published

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://docs.microsoft.com/en-us/archive/msdn-magazine/2017/may/net-core-cross-platform-code-generation-with-roslyn-and-net-core

https://blog.csdn.net/Crazy2910/article/details/106918516

 

如何实现运行时动态定义Controller类型?

https://www.cnblogs.com/artech/p/dynamic-controllers.html