【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 服务…

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

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

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

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

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

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

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

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

python最大堆heapq_Python-堆的实现与heapq(最小堆库函数)

目录简介堆是一个二叉树&#xff0c;它的每个父节点的值都只会小于或大于所有孩子节点(的值)。它使用了数组来实现&#xff1a;从零开始计数&#xff0c;对于所有的 k &#xff0c;都有 heap[k] < heap[2*k1] 和 heap[k] < heap[2*k2]。 为了便于比较&#xff0c;不存在的…

深入浅出 ASP.NET Core 与 Docker 入门课程说明

点击蓝字“角落的白板报”关注我哟加个“星标★”&#xff0c;好文必达&#xff01;深入浅出 ASP.NET Core 与 Docker 入门课程说明《深入浅出 ASP.NET Core 与 Docker 》是一门新的课程&#xff0c;本课程所有的内容全部免费&#xff0c;以图文配合视频的形式呈现。课程完整视…

微软将在新西兰建设其第一个数据中心区域

昨天新西兰各IT群都被一条消息刷屏了&#xff1a;详情可见&#xff1a;https://news.microsoft.com/en-nz/2020/05/06/aotearoa-disclosure/NZ的第一个Azure region region 是云计算的一个术语&#xff0c;也就是各大云运营商机房部署的位置。目前微软、亚马逊、谷歌等比较大的…

使用 kind 快速搭建一个 Kubernetes 测试环境

使用 kind 快速搭建一个 Kubernetes 测试环境Introkind&#xff08;Kubernetes IN Docker&#xff09; 是一个基于 docker 构建 Kubernetes 集群的工具&#xff0c;非常适合用来在本地搭建基于 Kubernetes 的开发/测试环境。想写一篇 kind 的文章很久了&#xff0c;但是之前的 …

麻雀虽小,五脏俱全

入职三年&#xff0c; 除了参与公司核心产品研发外&#xff0c;另外负责了一个2C的小项目&#xff1a;调用API拿到解析结果 & 计费。❝项目最初是.NetCore 1.0-Previewsqlite部署在IIS上&#xff0c;闲来没事&#xff0c;这个项目已经被我完全重写&#xff0c;在此记录一些…

内存迟迟下不去,可能你就差一个GC.Collect

一&#xff1a;背景1. 讲故事我们有一家top级的淘品牌店铺&#xff0c;为了后续的加速计算&#xff0c;在程序启动的时候灌入她家的核心数据到内存中&#xff0c;灌入完成后内存高达100G&#xff0c;虽然云上的机器内存有256G&#xff0c;然被这么划掉一半看着还是有一点心疼的…

Mayor's posters POJ - 2528 (离散化+线段树)

题意&#xff1a; 在1~10000000这个区间中读取n个海报的区间信息&#xff0c;后面的海报会覆 盖前面的海报&#xff0c;问最后能看到几张海报.&#xff08;本题是一道bug题下面会提&#xff09; 题目&#xff1a; The citizens of Bytetown, AB, could not stand that the c…

[PAT乙级]1018 锤子剪刀布

大家应该都会玩“锤子剪刀布”的游戏&#xff1a;两人同时给出手势&#xff0c;胜负规则如图所示&#xff1a; 现给出两人的交锋记录&#xff0c;请统计双方的胜、平、负次数&#xff0c;并且给出双方分别出什么手势的胜算最大。 输入格式&#xff1a; 输入第 1 行给出正整数 …

java 开源控件_一些好用的开源控件

工作两年&#xff0c;一直都在做些编码方面的表面功夫&#xff0c;实现了很多很炫的功能&#xff0c;在此写下一些体验。有些比较小的dll文件我会发上来&#xff0c;如果是开源组织的代码我会把地址附上&#xff0c;毕竟人家是会更新的。大家还有什么好用的开源控件欢迎补充。一…

揭秘 .NET 5 和Java 互操作

早早的.NET团队就立下了.NET和Java互操作的flag&#xff0c;如果你去翻一翻dotnet/runtime库&#xff0c;丝毫看不出来仓库内在搞支持。xamarin/java.interop库一直有Mono和Java互操作的实现&#xff0c;那么100%的实现.NET和Java互操作就是它&#xff0c;这两篇文章就是和你一…

java中的asList_Java Arrays.AsList原理及用法实例

java.util.Arrays的asList方法可以方便的将数组转化为集合&#xff0c;我们平时开发在初始化ArrayList时使用的比较多&#xff0c;可以简化代码&#xff0c;但这个静态方法asList()有几个坑需要注意:一. 如果对集合使用增加或删除元素的操作将会报错如下代码&#xff1a;List l…

为什么顶尖高手,都是长期主义者?

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份在当下这个浮躁的社会&#xff0c;很多人都认为“快”才是真理&#xff0c;“慢”就是原罪。大家都对一夜腾飞的故事津津乐道&#xff0c;却觉得坚守一生最终获得大成的故事不够性感。但事实是怎么样的呢&…

java中函数是什么_[一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念...

本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍什么是函数式编程?java程序员第一反应可能会理解成类的成员方法一类的东西此处并不是这个含义,更接近是数学上的函数看一下百度百科中关于函数的说明函数的定义&#xff1a;给定一个数集A&#xff0c;假设其…

分布式系统不得不说的CAP定理

21天学会C语言&#xff1f;3天学会弹钢琴&#xff1f;放弃一切错误方法&#xff0c;从今天开始“刻意练习”&#xff0c;因为这才是最强大的&#xff0c;也是唯一正确的学习方法。--《刻意练习》Anders Ericsson引言CAP问题已经成了计算机科学中一个研究领域&#xff0c;之前说…