Natasha 的设计

动态编译

Roslyn 为开发者提供了动态编译的接口, 允许我们以 C# 代码来编写 Emit 或 表达式树生成的程序集, 但是完成一个编译需要诸多步骤, 用户参与的操作也很多, 例如: 格式化整理语法树, 创建编译选项, 填充对应的引用程序集来支持语义检查和编译, 控制输出流等. 其中除了第一个语法树相对简单, 后面都需要开发者摸索完成. 毕竟 Roslyn 的文档不全, 甚至关于它的文档散落在其他边角章节, 比七龙珠都散. 那么在这种情况下使用 Natasha 无疑是非常好的选择.

Natasha 的便捷之处

Natasha 自发版以来,便集成有引用管理, 全局 Using 管理, 域管理, 这让开发者极大的减少了开发前的准备工作, 在便捷编译过程中, Natasha 支持引用覆盖, Using 覆盖,编译流到域的输出, 有了这三大保证, 开发者可更多的关注于动态功能逻辑的开发.

新版 Natasha 新增了语义过滤委托 API 以方便用户根据语义信息定制/重组自己的语法树, 并提供方法支持开发者管理引用版本, 另外保证了3种流的对外输出,即

  • dll : 程序集输出文件
  • pdb : 元数据调试信息
  • xml : 元数据结构及注释

整个编译过程中将会分3阶段抛出异常:

  1. 语法构建阶段,如果出错则抛出异常;
  2. 编译阶段, 如果编译失败则会抛出异常;
  3. 元数据转换阶段, 有些 API 是支持从 Assembly 到其他元数据获取和转换的, 转换失败则抛出异常.

Natasha 基本编译单元

Natasha 的基本编译单元为 AssemblyCSharpBuilder , 该单元整合了编译流程所需要的基本功能, 相比 Natasha 的模板而言, 它则是轻量级,底层的工作单元.

以下是使用方法:

首先引入 DotNetCore.Natasha.CSharp

最基本的编译操作

//Natasha 预热
NatashaInitializer.Preheating(/*...引用添加过滤器...例如:(item, name) => name!.Contains("IO")*/); string code = @"public class A{public string Name=""HelloWorld"";}"; //在花括号范围内圈定域,using 内的方法锁定了域的作用范围.
//Natasha 所有关于 Name 的 Api 如果不指定,默认为 GUID.
using (DomainManagement.Create(domainName)/Random().CreateScope())
{
AssemblyCSharpBuilder builder = new( /*....assenblyName....*/ );
builder.Add(code);
var type = builder.GetTypeFromShortName("A");
//...do sth...
} //手动指定域
AssemblyCSharpBuilder builder = new();
builder.Domain = DomainManagement.Random();
builder.Add(code);
var assembly = builder.GetAssembly();
//...do sth... //直接定位到委托
string code = @"public class A{public string Name=""HelloWorld""; public static string Get(){ return (new A()).Name; }}";
using (DomainManagement.Create("myDomain").CreateScope())
{
AssemblyCSharpBuilder builder = new("myAssembly");
builder.Add(code);
var func = builder.GetDelegateFromShortName<Func<string>>("A","Get");
Assert.Equal("HelloWorld", func()); // √
}
其他 API
//设置输出 dll 文件路径
builder.SetDllFilePath(mydll);
//设置输出 pdb 文件路径
builder.SetPdbFilePath(mypdb);
//设置输出 xml 文件路径
builder.SetXmlFilePath(myxml);
//使用 Natasha 自带的输出路径(请在域和程序集名确定之后调用).
builder.UseNatashaFileOut(); //配置编译选项
builder.ConfigCompilerOption(opt=>opt);
//配置语法树选项
builder.ConfigSyntaxOptions(opt=>opt); //给编译单元添加语义过滤
builder.AddSemanticAnalysistor();
//启/禁用语义过滤
builder.Enable/DisableSemanticCheck(); //添加日志事件
builder.LogCompilationEvent += (log) => { if(log.HasError) Console.WriteLine(log.ToString()); }; //编译事件
builder.CompileSucceedEvent //编译成功触发事件
builder.CompileFailedEvent //编译失败触发事件 //引用行为与程序集加载行为控制
var assembly = builder //委托过滤: 如果发现默认域的引用与定制域中的引用有同名情况,则进入委托处理. 返回一个枚举结果给程序处理.
//PassToNextHandler 结果表示将进入到引用版本行为控制继续处理
.CompileWithReferencesFilter((defaultAssemblyName,domainAssemblyName)=> LoadVersionResultEnum.PassToNextHandler) //引用行为控制, None/UseHighVersion/UseLowVersion/UseDefault(默认使用)/UseCustom 四种控制方法
.CompileWithReferenceLoadBehavior(referenceLoadBehavior) //程序集编译成功后,在域中加载的行为控制,默认为 LoadBehaviorEnum.None (全加载);
.CompileWithAssemblyLoadBehavior(LoadBehaviorEnum.UseDefault) .GetAssembly();

注意: 主域的引用文件和自己创建域的引用文件可能存在同名,但不同版本,此时编译需要 CompileWithReferenceLoadBehavior 来控制引用加载的行为, 举例: RefA(v1.0) 和 RefA(v2.0) 相比, v2.0 中比 v1.0 多了几个功能,几个类,几个接口.... 那么在管理引用的时候, 你就要根据自身的代码情况进行管理, 比如你的代码用到了 v2.0 的新类,新功能, 那么就要屏蔽掉 v1.0.

覆盖全局 using
//-------------------主域 using -------------------- 定制域 using ------------------------------- 代码脚本 ---------------
string code = DefaultUsing.UsingScript + builder.Domain.UsingRecorder.ToString() + "namespace{ public class xx..... }";

域中的 UsingRecorder 会记录编译之后产生的 using, 自动管理.

结尾

大家在使用动态编译时, 要尽可能做到"隔离", 一旦依赖和引用版本多了, 对于动态开发来讲,就是一场灾难.

以上是使用 Natasha 关于动态编译的最基本使用方法, 下一篇将讲解 Natasha 高级 API 的使用.

最新文章

  1. slice,substr和substring的区别
  2. ajax传值方式为数组
  3. PHP扩展开发
  4. ECS修改默认端口22及限制root登录
  5. 完全掌握Android Data Binding
  6. IPv6 tutorial – Part 5: Address types and global unicast addresses
  7. Scala学习笔记--Actor和并发
  8. 转 Oracle全文检索http://docs.oracle.com/cd/E11882_01/text.112/e24436/toc.htm
  9. Tar打包、压缩与解压缩到指定目录的方法
  10. mysql模糊匹配
  11. HTML基本介绍
  12. 收集下shell使用笔记
  13. Java线程池之ThreadPoolExecutor
  14. php使用websocket示例详解
  15. Flask源码流程剖析
  16. if else与switch区别
  17. iOS中 WGAFN_网络监控 技术分享
  18. 低延时的P2P HLS直播技术实践
  19. 爬虫免登录进入github
  20. wcf生成客户端代理类步骤及语句

热门文章

  1. Hive实战—时间滑动窗口计算
  2. JAVA使用WebSocket显示实时在线浏览人数
  3. SpringBoot 设置请求字符串格式为UTF-8
  4. 【LeetCode】1135. Connecting Cities With Minimum Cost 解题报告 (C++)
  5. 【LeetCode】87. Scramble String 解题报告(Python & C++)
  6. 如何在 Go 中将 []byte 转换为 io.Reader?
  7. HITCON 2019 Lost Modular again writeup
  8. Centos 切换中文输入法
  9. Java——HashMap集合详解
  10. CVE-2021-45232 Apache APISIX 从未授权访问到RCE