Natasha 4.0 探索之路系列(三) 基本的动态编译
相關文章
Natasha 4.0 探索之路系列(一) 概況
Natasha 4.0 探索之路系列(二) 「域」與插件
Natasha 的設計
動態編譯
Roslyn 為開發者提供了動態編譯的接口,允許我們以 C# 代碼來編寫 Emit 或 表達式樹生成的程序集,但是完成一個編譯需要諸多步驟,用戶參與的操作也很多,例如: 格式化整理語法樹,創建編譯選項,填充對應的引用程序集來支持語義檢查和編譯,控制輸出流等。其中除了第一個語法樹相對簡單,后面都需要開發者摸索完成。畢竟 Roslyn 的文檔不全,甚至關于它的文檔散落在其他邊角章節,比七龍珠都散。那么在這種情況下使用 Natasha 無疑是非常好的選擇。
Natasha 的便捷之處
Natasha 自發版以來,便集成有引用管理,全局 Using 管理,域管理,這讓開發者極大的減少了開發前的準備工作,在便捷編譯過程中,Natasha 支持引用覆蓋,Using 覆蓋,編譯流到域的輸出,有了這三大保證,開發者可更多的關注于動態功能邏輯的開發。新版 Natasha 新增了語義過濾委托 API 以方便用戶根據語義信息定制/重組自己的語法樹,并提供方法支持開發者管理引用版本,另外保證了3種流的對外輸出,即
dll : 程序集輸出文件
pdb : 元數據調試信息
xml : 元數據結構及注釋
整個編譯過程中將會分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 的使用。
總結
以上是生活随笔為你收集整理的Natasha 4.0 探索之路系列(三) 基本的动态编译的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WPF 实现动态Windows桌面壁纸~
- 下一篇: 推荐搞IT的你读读《软件随想录》