动态编译库 Natasha 5.0 于十月份发布,此次大版本更新带来了强大的兼容性支持,目前 Natasha 已支持 .NET Standard 2.0 及 .NET Core 3.1 以上版本(包括 .NET Framework)了。
NuGet\Install-Package DotNetCore.Natasha.CSharp -Version 5.0.0
从 Natasha 5.0 开始,将支持根据 TargetFramework 目标版本来适配对外 APIs,为开发者自动选择提供单域编译引擎或多域编译引擎。
Natasha 5.0 在源码层面分为 MultiDomain、Public 和 SingleDomain 三部分,并使用自定义宏 MULTI 来区分单域与多域。
Natasha 5.0 从工程文件层面上做了兼容性隔离,确保 Natasha 在后续版本的升级中不必过多关注兼容性问题,多域编译引擎依旧是 Natasha 未来版本的主要方向,后续的升级与优化工作也将在 MutliDomain 中开展。
如多域编译引擎提供了如下接口:
{OperatorClass}.DefaultDomain/CreateDomain/RandomDomain/UseDomain
其对应的单域编译引擎仅支持如下接口:
{OperatorClass}.DefaultDomain
单域编译引擎的编译结果会全部加载到主域当中,因此不具备域功能隔离与卸载能力。
使用 字符串脚本 需要对编译原理有一定程度的了解。Natasha 与 Roslyn 将为开发者极大地简化了复杂的理论依据和构建过程。开发者在使用 Natasha 时需要关注以下问题:
一、元数据管理问题
熟悉 Emit/Expression 的开发者了解,在构建过程中可能会使用到反射(如 PropertyInfo、FieldInfo、MethodInfo 等信息)。由于许多开发者在编写代码时,只关注反射的使用,忽视了这些元数据对动态编译的重要性,因此在通过字符串脚本进行编译时,时常会出现各种问题。
Natasha 和 Roslyn 同样需要元数据,而元数据的来源十分多样,如引用程序集、内存程序集、实际程序集等。其中,除内存程序集外,元数据均被记录在 DLL 文件中,因此当使用如下构建代码时:
NatashaManagement.AddGlobalReference("1.dll");
可能会引发如下异常:
找不到 RuntimeMetadataVersion 的值。找不到包含 System.Object 的程序集,或未通过选项为 RuntimeMetadataVersion 指定值。
引用管理对于程序来讲是具有一定负担的,因为目前还不能从内存程序集中提取到元数据,所以需要以文件的方式来添加,这也就导致了开发者在发布动态编译程序时,需要有完毕的引用文件跟随 —— 将导致所发布的包体积变大。至于环境需要哪些引用文件,可以交由 NCC 编译环境包来解决。如果开发者不能很好地管理引用,那么直接引入该包来全面覆盖当前程序的元数据是最好的选择。
二、Using 管理问题
Using 的管理关乎元数据的引用来源。任何一个动态构建,都是以一个完整类的方式进行的,因此完整的类 using 代码是必不可少的。Natasha 构建模板目前可以覆盖大部分 using,并能通过语义过滤来处理异常 using。如果开发者直接使用 AssemblyCSharpBuilder 来构建代码,那么就需要注意脚本中的 using 问题。
编译环境包目前已不再包含于 Natasha Package 中,因此需要使用以下方法来加载编译环境:
使用 Natasha 接口来管理全局引用与 Using 缓存
NatashaManagement.AddGlobalReference/AddGlobalUsing
使用 NCC 编译环境包来解决元信息的引用问题
DotNetCore.Compile.Environment
如果开发者认为生成的文件中有太多的多语言适配资源,那么可以使用 SatelliteResourceLanguages 来指定默认的资源语言:
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
二义性问题仍被归属到开发者的错误开发行为中,不应由 IDE 或 Natasha 来解决。Natasha 团队倾向于在命名空间发生冲突时,由开发者手工解决该问题,Natasha 的上下文语义环境并不能百分之百地推断出用户究竟想用哪一个命名空间。
目前有三种解决方案可供开发者选择:
使用 Natasha.CSharp.Extension.Ambiguity
扩展包,通过 .Using() 或 .ConfigUsing() 模板自带的方法来指定优先级最高的 using 。该包将在不久之后将独立并进入 Natasha 生态社区 NMS 之中;
直接使用引擎 AssemblyCSharpBuilder
编译字符串脚本,在字符串层面进行替换;
定制语义过滤方法,更新编译单元中的语法树,使用 Natasha 的语义扩展方法,将自定义语义过滤方法添加到 Natasha 中:
assemblyCSharpBuilder.AddSemanticAnalysistor(Func<AssembltCSharpBuilder, CSharpCompilation, CSharpCompilation>)
(需要有语法语义相关开发经验)
一个尽可能复杂的示例:
var action = NDelegate
//使用随机域 也可以使用 CreateDomain / UseDomain / DefaultDomain
//Core3.1以下仅能使用 DefaultDomain
.DefaultDomain()
//[可选API] 必要时使用 ConfigBuilder 配置编译单元(下面只为展示API, 有需求就用, 没需求不用写)
.ConfigBuilder(builder => builder
//配置编译器选项
.ConfigCompilerOption(opt => opt
//配置平台
.SetPlatform(Microsoft.CodeAnalysis.Platform.AnyCpu)
//Release 方式编译
.CompileAsRelease()
//开启可空警告
.SetNullableCompile(Microsoft.CodeAnalysis.NullableContextOptions.Warnings))
//配置语法选项
.ConfigSyntaxOptions(opt => opt
//配置支持的脚本语言版本
.WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8))
//禁用语义检查与过滤
.DisableSemanticCheck()
)
//[可选API] 配置该方法所在的类模板
.ConfigClass(item => item
//给类配置一个名字,不用随即名
.Name("myClass")
//不使用默认域的 Using 缓存
.NoGlobalUsing())
//[可选API] 为类模板添加 using 引用
.ConfigUsing("System")
//这里的 API 参照定义的委托, 包括委托的参数
//例如 Action<int> / Func<int,int> 拥有一个参数, 参数的名字请在 Action<int> / Func<int,int> 上 F12 查看定义获取参数名.
.Action("Console.WriteLine(\"Hello World!\");");
action(); /*Output: Hello World!*/
2022/09/05 - 2022/09/21
DotNetCore.SourceLink.Environment
依赖,支持 .NET Standard 2.0/2.1 版本DotNetCore.Compile.Environment
依赖,支持 .NET Standard 2.0/2.1 版本2022/09/30 - 2022/10/09