对于开源贡献者,Emit和表达式树不是陌生的字眼,IL的动态特性为封装工作带来了极大的方便,会Emit的开发者可以说驾驭了大部分的高性能、高动态的编程技巧。纵观ef、dapper、json.net等第三方常用库,哪个能脱离emit而独善其身,也正因如此,幸福了一批批懒癌患者,包括我这个懒癌中晚期患者(这里给各位病友问好),与此同时本人对封装有着莫名其妙的执念,就在两支怪力的驱使下走上了对emit的不归路.
旧版Natasha始于2016年,当时是对Emit进行的封装,中途经有柠檬的提醒完善了UT和兼容性等工作,后由Victor.X.Qu补充了文档,后经ORM实战。
经历过重重思考和实践,Emit不是动态的最佳实践,简单的从以下几个角度来讲:
尽管表达式树已经帮我们做了一些工作,但复杂场景和使用习惯仍然封印着开发者的大脑。
Roslyn到如今已经耳熟能详了,编译被当作成服务对外开放,让不少开发者从中受益,但由于文档不全,实例不充分,从开始一直到2018年期间,对于懒癌开发者来说,基于Roslyn开发都是一件憋手的事情(例如一些必备操作文档,在2019年今年5月份才提上日程)。Natasha使用Roslyn做为编译引擎,不仅仅在动态构建上进行了人性化升级,还在功能上进行了简化。您不仅可以使用Natasha轻松的构建类、结构体、方法、接口、抽象类,还可以轻松的继承类、重载方法、实现接口、抽象类等等,技术较新,仅支持.standard2.0。
项 目 地 址:https://github.com/dotnetcore/Natasha
Nuget索引:DotNetCore.Natasha (正式版1.0.0.0)
(娜塔莎)(原型苏联红军第25步兵师的中尉柳德米拉·帕夫利琴科,一名出色的女狙击手)
<PreserveCompilationContext>true</PreserveCompilationContext>
这几年随着.NET架构引擎的不断升级,dynamic、emit执行性能已经得到了大幅度提升,roslyn也不例外,之前官方给过性能测试截图,上面显示是比emit快一点,个人的基准测试要等下一个benchmark版本,从耗时的角度来说roslyn <= emit (roslyn有指定release模式编译),所以大家根本不用关心性能问题。
使用之前需要注意的是,方法操作都是基于内存流编译,类和其他都基于文件流编译。但有特殊情况,比如有些方法需要被复用,这时可以在编译选项中如此操作:
var tempBuilder = FastMethodOperator.New;
tempBuilder.ComplierOption.UseFileComplie();
//可根据需求选择编译方式
var builder = new ClassBuilder();
builder.ComplierOption.UseFileComplie();
builder.ComplierOption.UseMemoryComplie();
public abstract class TestAbstract
{
public int Name;
public int Age;
public abstract int GetAge();
public abstract string GetName();
}
OopOperator<TestAbstract> abstractBuilder = new OopOperator<TestAbstract>();
abstractBuilder.ClassName("UTestClass");
abstractBuilder["GetName"] = "return Name;";
abstractBuilder["GetAge"] = "return Age;";
abstractBuilder.Compile();
TestAbstract test = abstractBuilder.Create("UTestClass");
public interface ITest
{
int MethodWidthReturnInt();
string MethodWidthReturnString();
void MethodWidthParamsRefInt(ref int i);
string MethodWidthParamsString(string str);
string MethodWidthParams(int a,string str,int b);
}
OopOperator<ITest> interfaceBuilder = new OopOperator<ITest>();
interfaceBuilder.ClassName("UTestClass");
interfaceBuilder["MethodWidthReturnInt"] = "return 123456;";
interfaceBuilder["MethodWidthReturnString"] = "return \"test\";";
interfaceBuilder["MethodWidthParamsRefInt"] = "i+=10;";
interfaceBuilder["MethodWidthParamsString"] = "return str+\"1\";";
interfaceBuilder["MethodWidthParams"] = "return a.ToString()+str+b.ToString();";
interfaceBuilder.Compile();
ITest test = interfaceBuilder.Create("UTestClass");
public delegate string GetterDelegate(int value);
//方法一
var action = DelegateOperator<GetterDelegate>.Create("value += 101; return value.ToString();");
//action(1); result: "102"
//方法二
var action = "value += 101; return value.ToString();".Create<GetterDelegate>();
//action(1); result: "102"
var action = FastMethodOperator.New
.Param<string>("str1")
.Param(typeof(string),"str2")
.MethodBody("return str1+str2;")
.Return<string>()
.Complie<Func<string,string,string>>();
string result = action("Hello ","World!");
//result: "Hello World!"
//这里只为演示,实际使用请用下面的静态构造,拿到委托后可以直接运行。
public class TestB
{
public void TestMethod(){}
}
var action = FakeMethodOperator.New
.UseMethod<TestB>("TestMethod")
.MethodContent($@"Console.WriteLine(""Hello World!"");")
.Complie<Action>();
//The class script :
//
// using System;
// public class N20d26dcba7e6451eaf4c4a6f4753e243
// {
// public void TestMethod()
// {
// Console.WriteLine("Hello World!");
// }
// }
var action = FakeMethodOperator.New
.UseMethod<TestB>("TestMethod")
.StaticMethodContent($@"Console.WriteLine(""Hello World!"");")
.Complie<Action>();
//The class script :
//
// using System;
// public static class Neae0b5f6b8b94f4b9418ebc68813760b
// {
// public static void TestMethod()
// {
// Console.WriteLine("Hello World!");
// }
// }
ClassBuilder builder = new ClassBuilder();
var script = builder
.Namespace("TestNamespace")
.ClassAccess(AccessTypes.Private)
.ClassModifier(Modifiers.Abstract)
.ClassName("TestUt2")
.ClassBody(@"public static void Test(){}")
.PublicStaticField<string>("Name")
.PrivateStaticField<int>("_age")
.Builder().Script;
Assert.Equal(
@"using System;namespace TestNamespace{private abstract class TestUt2{public static String Name;private static Int32 _age;public static void Test(){}}}"
, script);
自编译一个类
string text = @"using System;
using System.Collections;
using System.Linq;
using System.Text;
namespace HelloWorld
{
public class TestIndex1
{
public string Name;
public int Age{get;set;}
}
public class TestIndex2
{
public string Name;
public int Age{get;set;}
}
public class TestIndex3
{
public string Name;
public int Age{get;set;}
}
}
namespace HelloWorld{
public struct TestStruct1{}
public struct TestStruct2{}
public class TestIndex4
{
public string Name;
public int Age{get;set;}
}
}";
//根据脚本创建动态类
//寻找第二个命名空间中的第一个类
Type type = RuntimeComplier.GetClassType(text, 1,2);
Assert.Equal("TestIndex4", type.Name);
//寻找第二个命名空间中的第二个结构体
type = RuntimeComplier.GetStructType(text, 2, 2);
Assert.Equal("TestStruct2", type.Name);
Example:
typeof(Dictionary<string,List<int>>[]).GetDevelopName();
//result: "Dictionary<String,List<Int32>>[]"
typeof(Dictionary<string,List<int>>[]).GetAvailableName();
//result: "Dictionary_String_List_Int32____"
typeof(Dictionary<string,List<int>>).GetAllGenericTypes();
//result: [string,list<>,int]
typeof(Dictionary<string,List<int>>).IsImplementFrom<IDictionary>();
//result: true
typeof(Dictionary<string,List<int>>).IsOnceType();
//result: false
typeof(List<>).With(typeof(int));
//result: List<int>
Example:
Using : Natasha.Method;
public delegate int AddOne(int value);
var action = "return value + 1;".Create<AddOne>();
var result = action(9);
//result : 10
var action = typeof(AddOne).Create("return value + 1;");
var result = action(9);
//result : 10
Example:
Using : Natasha.Clone;
var instance = new ClassA();
var result = instance.Clone();
Example:
Using : Natasha.Snapshot;
var instance = new ClassA();
instance.MakeSnapshot();
// ********
// do sth
// ********
var result = instance.Compare();
由于核心编译引擎为Roslyn,因此语法检查、词法检查、语义检查等都支持,这样可以很好的为开发者提供错误提示, Natasha为此增加日志模块(NScriptLog), 在编译流程中,捕获编译信息并记录,另需注意的是,Natasha脚本的格式化操作与VS的格式化一样,所以需要开发者在构建脚本的时候就要多多注意换行的问题。Natasha的编译日志共有3个种类:成功日志、错误日志、警告日志。
九、寄语
随着.NETCore的不断升级,Natasha还有很多新的特性在等待开发,希望各位多多支持。https://github.com/night-moon-studio NMS是一个基于Natasha的开源项目“孵化”组,项目成熟且通过审核之后(或改名)推荐进入NCC,大家可以积极参与。
Natasha如果说是一个库不如说是一个时机,动态封装已经不是学习成本高、开发效率低的工作了,希望各位能积极参与尝试开源项目,共同打造.NET Core新生态。