【A】 Natasha3.0 引擎亮给你,请你来折腾

 文章转载授权级别:A

一 、 引言

Natasha 距离上个 2.+ 版本大概有1个月了,在4月份里我把模板与引擎进行了重构,旨在更抽象、规范、合理,方便其他人参与开源、定制。接下来我将从 引擎的结构 、类库的使用及新热的 Source Generators 技术进行一些讲解:

二 、 Natasha.Framework 

框架结构示意图:

【FrameworkAPI  预览】https://natasha.dotnetcore.xyz/zh/framework/framework.html

三大基础类:

1、域操作类 DomainBase , 在动态开发的环境中,域尤为重要,这是隔离 程序集污染 的重要手段,也是你反复折腾编译的基本单元容器。而 DomainManagement 则是我封装的一个认为比较合理的 域管理操作类 ,至少可以让开发者对域的弱管理这块操心少一点。

2、语法操作类 SyntaxBase ,  如果说域是基本单元容器,那么 程序集 就是你编译的基本单元,组成程序集可以是很多个类,枚举,接口等等,它们在动态编译流程中是以 SyntaxTree 形式存在,编译时这些 SyntaxTree 最终将会编译到一个程序集中。

一个很简单的流程就是:字符串 -> 树 -> 某域中的程序集 。

同时你还需要知道,C# 有 C#的语法树(CSharpSyntaxTree),VB 有 VB的语法树 (VisualBasicSyntaxTree)等,SyntaxTree 作为抽象标准,您只需实现方法并制造返回你需要的语法树即可。

3、编译操作类 CompilerBase, 这个类抽象出了编译行为,不管是 C# 还是 VB,只要你实现方法并返回你需要的语言的编译信息即可。之所以同一个方法能返回不同的编译信息,是因为它们都是继承自 Compilation 类,这个类是基础的是编译的关键所在(包括前两天挺热的 Source Generators 技术)。

三 、 实现 

如图所示:

图中将上面所述的三大基础类各自实现了 CSharp 的功能并封装成库,Natasha 的 C# 编译引擎由这几个库构成,它们在 CSharpEngine 库中完成协作,编译的流程在 CSharpEngine 中得到了更好、更严格的控制。


四、 API 与 Samples

Natasha 3.0 在 API 上做出了一些更为标准和舒适的改变: 

  • 原快速构建委托的 NDomain 更名为 NDelegate

  • 原基础 C# 编译器 AssemblyCompiler 更名为 AssemblyCSharpBuilder 

如何开始:

  • 引入 DotNetCore.Natasha 库(最新版为3.0.0.1, 修复了跨平台的BUG); 

  • 引入 编译环境库 :DotNetCore.Compile.Environment  ;

  • 向引擎中注入定制的域:DomainManagement.RegisterDefault< AssemblyDomain >(); 

  • 敲代码;

下面我将拿出一些例子来展示动态编译的基本操作:

静态初始化:

在静态构造上进行了复用与改进:

Natasha 的所有模板均继承自 CompilerTemplate ,CompilerTemplate 本身会提供静态构造方法。因此上层 API 也会被支持。

NDelegate / NAssembly / NClass.. / xxx_Oerator 等等均拥有该静态构造方法,以下称为 “Handler”.

//使用 domain 域
Handler.UseDomain(domian, compiler => { 编译器配置 });//使用某编译器的域
Handler.UseCompiler(assemblyCSharpBuilder, compiler => { 编译器配置 }));//创建一个叫 "domainJim" 域
Handler.CreateDomain("domianJim", compiler => { 编译器配置 });//使用默认域
Handler.DefaultDomain(compiler => { 编译器配置 });//使用随机域
Handler.RandomDomain(compiler => { 编译器配置 });

编译器配置:

builder => 
{builder.CustomerUsing()                    //使用用户自定义的Using.SetAssemblyName("MyAssemblyName")  //设置程序集名.ThrowAndLogCompilerError()         //抛出并记录编译器的异常.ThrowSyntaxError()                 //抛出语法树异常.UseStreamCompile();                //使用流编译
}

字符串编译:

//引擎开放之后,您可以向引擎中注入自己实现的域
//这里的 AssemblyDomain 是 Natasha 实现的域
DomainManagement.RegisterDefault<AssemblyDomain>();//使用 Natasha 的 CSharp 编译器直接编译字符串
AssemblyCSharpBuilder sharpBuilder = new AssemblyCSharpBuilder();//给编译器指定一个随机域
sharpBuilder.Compiler.Domain = DomainManagement.Random;//使用文件编译模式,动态的程序集将编译进入DLL文件中
//当然了你也可以使用内存流模式。
sharpBuilder.UseFileCompile();//如果代码编译错误,那么抛出并且记录日志。
sharpBuilder.ThrowAndLogCompilerError();
//如果语法检测时出错,那么抛出并记录日志,该步骤在编译之前。
sharpBuilder.ThrowAndLogSyntaxError();//添加你的字符串
sharpBuilder.Syntax.Add(@"
using System; 
public static class Test{ public static void Show(){ Console.WriteLine(\"Hello World!\");}
}");
//编译出一个程序集
var assembly = sharpBuilder.GetAssembly();//如果你想直接获取到类型
var type = sharpBuilder.GetTypeFromShortName("Test");
type = sharpBuilder.GetTypeFromFullName("xxNamespace.xxClassName");
//同时还有
GetMethodFromShortName
GetMethodFromFullName
GetDelegateFromFullName
GetDelegateFromFullName<T>
GetDelegateFromShortName
GetDelegateFromShortName<T>//创建一个 Action 委托
//必须在同一域内,因此指定域
//写调用脚本,把刚才的程序集扔进去,这样会自动添加using引用
var action = NDelegate.UseDomain(sharpBuilder.Compiler.Domain).Action("Test.Show();", assembly);
//运行,看到 Hello World!
action();

快速 API :

NClass:

NClass builder = NClass.RandomDomain(); //使用随机域
var script = builder.CurstomeUsing()    //仅使用构建过程中搜集到的using.HiddenNamespace()  //不用命名空间.Access(Natasha.Reverser.Model.AccessTypes.Public)  //保护级别:公有.DefinedName("EnumUT1")  //类名 .Field(item=> { item.Public().DefinedName("Apple").DefinedType<int>(); }).Field(item => { item.Public().DefinedName("Orange").DefinedType<string>(); }).Property(item => { item.Public().DefinedName("Banana").DefinedType<NClass>(); }).Script;//上述 Script 结果
using System;
using Natasha.CSharp;
public class EnumUT1
{public System.Int32 Apple;public System.String Orange;public Natasha.CSharp.NClass Banana{get;set;}
}
var type = builder.GetType();

NStruct:

NStruct builder = NStruct.RandomDomain();
var script = builder.CurstomeUsing().HiddenNamespace().Attribute("[StructLayout(LayoutKind.Explicit)]").Access(AccessTypes.Public).DefinedName("XXXName").Field(item => { item.Attribute<FieldOffsetAttribute>("0").Public().DefinedName("Apple").DefinedType<int>(); }).Field(item => { item.Attribute<FieldOffsetAttribute>("0").Public().DefinedName("Orange").DefinedType<int>(); }).Script;//上述 Script 结果
using System.Runtime.InteropServices;
using System;
[StructLayout(LayoutKind.Explicit)]
public struct XXXName
{[System.Runtime.InteropServices.FieldOffsetAttribute(0)]public System.Int32 Apple;[System.Runtime.InteropServices.FieldOffsetAttribute(0)]public System.Int32 Orange;
}
var type = builder.GetType();

更多的例子详见:https://natasha.dotnetcore.xyz/ 

插件:

对于插件的使用如下:

// 区分流加载和文件加载
var assembly = domain.LoadPluginFromStream(path);
assembly = domain.LoadPluginFromFile(path);// 将引用从引用表中移除
domain.Remove(Assembly);
domain.Remove(path);// 后面的 assembly 一定要加上,这样保证正常的 using 引用。
var func = NDelegate.UseDomain(domain).Func<IPlugin>("return new MyPlugin();", assembly);
var iPlugin = func();

虽然,有些插件的依赖可以被动态的替换,但这里我强烈建议使用域做热更新,插件不要了就把域卸载。

五、 浅谈 Source Generators

文章地址 :https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/

对于动态构建,我分为三类:

  • 编译前动态,比如定制代码段,IDE提示,CLI生成框架,代码生成器等等.

  • 编译时动态,根据框架约束\IDE分析器 在编译的过程中将代码整理并注入生成结果,今儿的 Source Generators 还有 Mono.Cecils等等。

  • 运行时动态,程序跑起来之后,根据不同的场景,动态生成所需的代码, 反射、Emit、Expression、Roslyn 及 Roslyn 之上的 Natasha。

Source Generators :源发生器?源码生成器?那不管怎么叫它,都跟源码和生成相关,目前是预览阶段,官方建议把改造的功能单独放在一个 stanadard2.0 类库工程中,资料显示,这东西是一个随着编译器和分析器一起加载的 .netstandard2.0 程序集, .NET Standard 组件能加载,它才能用。

所有的改造操作都需要在继承 ISourceGenerator 接口之后实现方法来操作,其参数中可以拿到 Compilation,对语法树集合进行一些改造,然后交由编译器再编译。

而引用它的那些工程,需要将它作为分析器添加到工程,同时需要将它作为类库引用到工程,然后写一些约定好的代码,在分析器的帮助下,它将不会报错,这样在编译阶段,将自动完成动态的构建。(上述描述若有错还请提醒~)

Roslyn 的一小步,AOT 的一大步?对于这方面的应用和封装我真的建议是再等等,再等等 = =。

六、 共同打造.NET生态

笔者还在找工作,很焦虑也很慌乱,但每次投身于 Natasha 中都会有莫名的心安,心里一直有个强烈的声音在说 Natasha 不是你个人技术生涯的终点,而是一个开源生态的起点。最近我又找到了一个新的激励自己的理由:如果你累了就想想 FreeSql 那上千个单元测试, 这阶段的难都懂,一起努力吧。

近期越来越多的开源项目涌现出来, 越来越多的好项目加入 NCC , 大家在慢慢的凝聚,廉颇不老,参与不晚,希望大家多多参与和支持 NCC 社区。

七、 鸣谢

感谢 “天天向上卡索” 对 Natasha 的一些需求建议及代码审计。

感谢 “吃着公粮” 对 Natasha 运行时反解器提供的一些技术建议及改进灵感。

感谢 FreeSql 叶老提供的需求。

感谢 WTM 团队 Vito 提供跨平台的测试信息。

感谢 Swift.Json 牛逼哥讨论的指令优化,已加入 Project。

感谢 lifecoach、Torch_zjx、cqx 等小伙伴的使用体验回馈。

本人一直认为开源项目的作者不是一个人,而是一个群体,正如 nuget 里的版权声明一样:.NET Core Community and Contributors ,后续 Authors 将根据情况进行更改。


https://github.com/dotnetcore

打赏一杯酒,削减三分愁。
跟着我们走,脱发包你有。

组织打赏账户为柠檬的账户,请标注「NCC」,并留下您的名字,以下地址可查看收支明细:https://github.com/dotnetcore/Home/blob/master/Statement-of-Income-and-Expense.md

OpenNCC,专注.NET技术的公众号

https://www.dotnetcore.xyz

微信ID:OpenNCC

长按左侧二维码关注

欢迎打赏组织

给予我们更多的支持

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310153.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux ps mysql_linux系统中ps指令使用详解

在linux系统作为和unix和ubuntu相同的系统&#xff0c;ps指令经常被用到查看程序进程的状态&#xff0c;但是这个指令具体怎么用您会吗&#xff1f;本文就以centos为例&#xff0c;结合项目中服务器的实际应用&#xff0c;给大家讲解下ps指令的用法。一、参数a——显示现行终端…

ASP.NET Core在CentOS上的最小化部署实践

引言本文从Linux小白的视角&#xff0c; 在CentOS服务器上搭建一个Nginx-Powered AspNet Core Web准生产应用。在开始之前&#xff0c;我们还是重温一下部署原理&#xff0c;正如你所常见的.Net Core 部署图&#xff1a;在Linux上部署.Net Core App最好的方式是使用Kestrel 服务…

Pearls POJ - 1260(区间记忆化搜索)

题意&#xff1a; n件物品&#xff0c;给出数量和价格&#xff0c;&#xff08;注意数量和价格都是升序给出的这个是能DP的关键&#xff09;&#xff0c;要买掉所以商品 对于每类物品&#xff0c;所需要的价格是(a[i]10)*p[i] &#xff0c;即要多买10件&#xff0c;也可以把价格…

[PAT乙级]1006 换个格式输出整数

让我们用字母 B 来表示“百”、字母 S 表示“十”&#xff0c;用 12…n 来表示不为零的个位数字 n&#xff08;<10&#xff09;&#xff0c;换个格式来输出任一个不超过 3 位的正整数。例如 234 应该被输出为 BBSSS1234&#xff0c;因为它有 2 个“百”、3 个“十”、以及个…

bigdecimal 平均数_MapReduce实例-必须用Combine--求平均数

本身求平均数很简单的&#xff0c;必须用到combine的话我在两个地方废了很多时间&#xff0c;一是combine的输入不仅仅是map的输出&#xff0c;还有可能是combine的输出&#xff0c;所以对value的处理得分两种情况吧&#xff1b;二是结果要保留4位有效数字。。。噗&#xff0c;…

面试官:你不懂六大设计原则,回去等通知吧!

一、前言不知道大家是否有这样的体会&#xff0c;就是在学习设计模式的时候&#xff0c;看了很多书籍&#xff0c;也照着很多示例把每个模式挨个敲了几遍&#xff0c;但过了一段时间后&#xff0c;就会忘了一大半。或者有的朋友尝试在业务编码中使用&#xff0c;却越用越复杂&a…

Dollar Dayz POJ - 3181(动态规划+大数高低位分离输出)

题意&#xff1a;就是给出二个数N&#xff0c;和k&#xff0c;有1~k种钱币&#xff0c;每种都 是无限个&#xff0c;用这些种类的钱币可以组合成总钱N有多少种方式。 解题&#xff1a;这就是一个完全背包&#xff0c;把N看成容量&#xff0c;钱币的类型值为 花费和价值。与记录…

[PAT乙级]1007 素数对猜想

让我们定义d​n​​为&#xff1a;d​n​​p​n1​​−p​n​​&#xff0c;其中p​i​​是第i个素数。显然有d​1​​1&#xff0c;且对于n>1有d​n​​是偶数。“素数对猜想”认为“存在无穷多对相邻且差为2的素数”。 现给定任意正整数N(<10​5​​)&#xff0c;请计…

is this mysql server_Mysql:is not allowed to connect to this MySQL server

如果你想连接你的mysql的时候发生这个错误&#xff1a;ERROR 1130: Host 192.168.1.3 is not allowed to connect to this MySQL server解决方法&#xff1a;1。 改表法。可能是你的帐号不允许从远程登陆&#xff0c;只能在localhost。这个时候只要在localhost的那台电脑&#…

Asp.Net Core Filter 深入浅出的那些事-AOP

一、前言在分享ASP.NET Core Filter 使用之前&#xff0c;先来谈谈AOP,什么是AOP 呢&#xff1f;AOP全称Aspect Oriented Programming意为面向切面编程&#xff0c;也叫做面向方法编程&#xff0c;是通过预编译方式和运行期动态代理的方式实现不修改源代码的情况下给程序动态统…

C++函数模板和普通函数的调用规则

C函数模板和普通函数的调用规则: 普通函数可以进行自动类型转换。 函数模板必须严格类型匹配。 C编译器优先考虑普通函数。 如果函数模板可以产生一个更好的匹配&#xff0c;那么选择模板。 可以通过空模板实参列表的语法限定编译器只能通过模板匹配。 代码如下&#xff…

A Mini Locomotive POJ - 1976(动态规划+思维)

题意&#xff1a;有三个火车头&#xff0c;n个车厢&#xff0c;每个车厢里面对应的有一定的人数。规定每个火车头最多 拉m个连续的车厢而且他们拉的车厢一定是从左到右连续的&#xff0c;问它能够拉的最多的人数&#xff1b; 思路&#xff1a;类似01背包的解法&#xff0c;首先…

mysql如何管理innodb元数据_MySQL 8 InnoDB 集群管理

使用 dba.checkInstanceConfiguration()在添加实例到集群中前&#xff0c;使用该方法检查实例配置是否满足InnoDB 集群要求。使用 dba.configureLocalInstance() 配置实例在MySQL Server版本不支持持久化功能的实例上&#xff0c;需要使用该方法添加修改配置信息到本地实例的选…

c编译过程概述

index.cpp是如何变成index.exe&#xff1f; 过程如下: #mermaid-svg-TCJ1Rm4qFgAObpkX .label{font-family:trebuchet ms, verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-TCJ1Rm4qFgAObpkX .label text{fill:#333}#mermaid-svg-T…

.NET Core技术研究-通过Roslyn代码分析技术规范提升代码质量

随着团队越来越多&#xff0c;越来越大&#xff0c;需求更迭越来越快&#xff0c;每天提交的代码变更由原先的2位数&#xff0c;暴涨到3位数&#xff0c;每天几百次代码Check In&#xff0c;补丁提交&#xff0c;大量的代码审查消耗了大量的资源投入。如何确保提交代码的质量和…

FATE HDU - 2159(二维完全背包)

限制条件&#xff1a; 1.忍耐度 m 2.杀怪个数 s 构造&#xff1a; dp[m][s] 得到的经验值 Time limit 1000 ms Memory limit 32768 kB OS Windows Source 2008信息工程学院集训队——选拔赛 最近xhd正在玩一款叫做FATE的游戏&#xff0c;为了…

mysql数据库check命令_利用mysqlcheck命令快速修复mysql数据库

表索引异常,修复msql表索引(表引擎:myisam)myisamchk --safe-recover /usr/local/mysql/data/ename_news/dede_arccacherepair table customerquestion;Error infos: Table ./ename_news/dede_arccache is marked as crashed and should be repairedmyisamchk -r data/ename_ne…

python中类变量的访问方式_在Python中,如何访问类方法中的“静态”类变量

就像所有的好例子一样&#xff0c;你简化了你实际想要做的事情。这很好&#xff0c;但值得注意的是&#xff0c;python在类和实例变量方面有很大的灵活性。方法也是如此。为了获得很好的可能性&#xff0c;我建议阅读Michael Ftsch new-style classes introduction&#xff0c;…

[PAT乙级]1009 说反话

给定一句英语&#xff0c;要求你编写程序&#xff0c;将句中所有单词的顺序颠倒输出。 输入格式&#xff1a; 测试输入包含一个测试用例&#xff0c;在一行内给出总长度不超过 80 的字符串。字符串由若干单词和若干空格组成&#xff0c;其中单词是由英文字母&#xff08;大小写…

Cheapest Palindrome POJ - 3280(动态规划*)

题意&#xff1a; 给出一个字符串&#xff0c;要求将其修改成一个回文字符串&#xff0c;给出修改某种字母&#xff08;添加或删除&#xff09;的价值&#xff0c;求最小使其成为回文字符串的价值。 题解&#xff1a; 感觉是求最长回文子序列的变形&#xff0c;然而刚开始想着…