基于 Roslyn 實(shí)現(xiàn)代碼動(dòng)態(tài)編譯
基于 Roslyn 實(shí)現(xiàn)代碼動(dòng)態(tài)編譯
Intro
之前做的一個(gè)數(shù)據(jù)庫小工具可以支持根據(jù) Model 代碼文件生成創(chuàng)建表的 sql 語句,原來是基于 CodeDom 實(shí)現(xiàn)的,后來改成使用基于 Roslyn 去做了。
實(shí)現(xiàn)的原理在于編譯選擇的Model 文件生成一個(gè)程序集,再從這個(gè)程序集中拿到 Model (數(shù)據(jù)庫表)信息以及屬性信息(數(shù)據(jù)庫表字段信息),拿到數(shù)據(jù)庫表以及表字段信息之后就根據(jù)數(shù)據(jù)庫類型生成大致的創(chuàng)建表的 sql 語句。
最近做的一個(gè)小工具 dotnet-exec 也是類似的,將代碼編譯成一個(gè)程序集并通過反射的方式執(zhí)行代碼邏輯,
分享一下用到的一些代碼
Sample
來看一個(gè)最簡單的編譯一段文本為程序集示例:
//?分析語法樹
var?syntaxTree?=?CSharpSyntaxTree.ParseText(sourceText,?new?CSharpParseOptions(LanguageVersion.Latest));
//?配置引用
var?references?=?new[]
{
????typeof(object).Assembly,
????Assembly.Load("netstandard"),
????Assembly.Load("System.Runtime"),
}
.Select(assembly?=>?assembly.Location)
????.Distinct()
????.Select(l?=>?MetadataReference.CreateFromFile(l))
????.Cast()
????.ToArray();
var?assemblyName?=?$"DbTool.DynamicGenerated.{GuidIdGenerator.Instance.NewId()}";
//?獲取編譯
var?compilation?=?CSharpCompilation.Create(assemblyName)
????.WithOptions(new?CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
????.AddReferences(references)
????.AddSyntaxTrees(syntaxTree);
using?var?ms?=?new?MemoryStream();
//?生成編譯結(jié)果并導(dǎo)出程序集信息到?stream?中
var?compilationResult?=?compilation.Emit(ms);
if?(compilationResult.Success)
{
????var?assemblyBytes?=?ms.ToArray();
????//?加載程序集
????return?Assembly.Load(assemblyBytes);
}
var?error?=?new?StringBuilder();
foreach?(var?t?in?compilationResult.Diagnostics)
{
????error.AppendLine($"{t.GetMessage()}");
}
throw?new?ArgumentException($"Compile?error:{Environment.NewLine}{error}");
多段文本的編譯示例:
var?parseOptions?=?new?CSharpParseOptions(LanguageVersion.Latest);
var?syntaxTrees?=?sourceText
????.Select(text?=>?CSharpSyntaxTree.ParseText(text))
????.ToArray();
var?references?=?new[]
{
????typeof(object).Assembly,
????Assembly.Load("netstandard"),
????Assembly.Load("System.Runtime"),
}
.Select(assembly?=>?assembly.Location)
????.Distinct()
????.Select(l?=>?MetadataReference.CreateFromFile(l))
????.Cast()
????.ToArray();
var?assemblyName?=?$"DbTool.DynamicGenerated.{GuidIdGenerator.Instance.NewId()}";
var?compilationOptions?=?new?CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
var?compilation?=?CSharpCompilation.Create(assemblyName,?syntaxTrees,?references,?compilationOptions);
await?using?var?ms?=?new?MemoryStream();
var?compilationResult?=?compilation.Emit(ms);
if?(compilationResult.Success)
{
????var?assemblyBytes?=?ms.ToArray();
????return?Assembly.Load(assemblyBytes);
}
var?error?=?new?StringBuilder();
foreach?(var?t?in?compilationResult.Diagnostics)
{
????var?msg?=?CSharpDiagnosticFormatter.Instance.Format(t);
????error.AppendLine($"{msg}");
}
throw?new?ArgumentException($"Compile?error:{error}");
之前的做法是合并成一段文本,并將多段代碼的 using 引用合并,可以參考下面的將多個(gè)文件代碼合并成一段文本,后來發(fā)現(xiàn)自己傻了,改成了上面的用法,直接生成多個(gè)語法樹再生成編譯,推薦使用上面的方式,會(huì)更加的友好和
var?usingList?=?new?List<string>();
var?sourceCodeTextBuilder?=?new?StringBuilder();
foreach?(var?path?in?sourceFilePaths.Distinct())
{
????foreach?(var?line?in?File.ReadAllLines(path))
????{
????????if?(line.StartsWith("using?")?&&?line.EndsWith(";"))
????????{
????????????usingList.AddIfNotContains(line);
????????}
????????else
????????{
????????????sourceCodeTextBuilder.AppendLine(line);
????????}
????}
}
var?sourceCodeText?=
????$"{usingList.StringJoin(Environment.NewLine)}{Environment.NewLine}{sourceCodeTextBuilder}";
More
如果需要指定 C# 代碼版本可以通過CSharpParseOptions 來指定,比如要使用 preview 特性可以使用 new CSharpParseOptions(LanguageVersion.Preview)
默認(rèn)地編譯會(huì)編譯成一個(gè) dll 程序集,如果包含 Main 方法要生成一個(gè)可執(zhí)行程序可以通過指定 CSharpCompilationOptions 的 OutputKind 為 OutputKind.ConsoleApplication, 還有很多可以配置的選項(xiàng),有需要可以自己探索一下
References
https://github.com/WeihanLi/DbTool/blob/packages/src/DbTool.Core/DefaultModelCodeExactor.cs https://github.com/WeihanLi/DbTool.Packages/blob/main/src/DbTool.Core/DefaultCSharpModelCodeExtractor.cs https://mp.weixin.qq.com/s?__biz=Mzg5MDEzNjA3Nw==&mid=2247483821&idx=1&sn=c2f4a672bc9bb1f939cdaaebb26eb8ac&chksm=cfe072cff897fbd9a0de68eec9a45a21d63b943d497723853b8944c5a5c48daede13d16db048&scene=21#wechat_redirect https://github.com/WeihanLi/dotnet-exec/blob/main/src/dotnet-exec/CodeCompiler.cs
