Natasha 4.0 探索之路系列(三) 基本的动态编译

013eb30c174283b60b91e1b540d9482f.png相关文章

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 的使用。

a0018d62250c491a6f1bc8b75f5157f3.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值