.NET高级特性-Emit
.NET高级特性-Emit基础
在这个大数据/云计算/人工智能研发普及的时代,Python的崛起以及Javascript的前后端的侵略,程序员与企业似乎越来越青睐动态语言所带来的便捷性与高效性,即使静态语言在性能,错误检查等方面的优于静态语言。对于.NETer来说,.NET做为一门静态语言,我们不仅要打好.NET的基本功,如基本类型/语法/底层原理/错误检查等知识,也要深入理解.NET的一些高级特性,来为你的工作减轻负担和提高代码质量。
ok,咱们今天开始聊一聊.NET中的Emit。
一、什么是Emit?
我们知道.NET可以由各种语言进行编写,比如VB,C++等,当然绝大部分程序员进行.NET开发都是使用C#语言进行的,这些语言都会被各自的语言解释器解释为IL语言并执行,而Emit类库的作用就是用这些语言来编写生成IL语言,并交给CLR(公共语言运行时)进行执行。
我们先来看看IL语言长什么样子:
(1) 首先我们创建一个Hello,World程序
class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); } }
(2) 将程序编译成dll文件,我们可以看到在开发目录下生成了bin文件夹
(3) 向下寻找,我们可以看到dll文件已经生成,笔者使用netcore3进行开发,故路径为bin/Debug/netcoreapp3.0
(4) 这时候,我们就要祭出我们的il查看神器了,ildasm工具
如何找到这个工具?打开开始菜单,找到Visual Studio文件夹,打开Developer Command Prompt,在打开的命令行中键入ildasm回车即可,笔者使用vs2019进行演示,其它vs版本操作方法均一致
(5) 在dasm菜单栏选择文件->打开,选择刚刚生成的dll文件
(6) 即可查看生成il代码
有了ildasm的辅助,我们就能够更好的了解IL语言以及如何编写IL语言,此外,Visual Studio中还有许多插件支持查看il代码,比如JetBrains出品的Resharper插件等,如果觉得笔者方式较为麻烦可以使用以上插件查看il代码
三、理解IL代码
既然IL代码咱们理解的差不多了,咱们就开始尝试用C#来写IL代码了,有了IL代码的参考,咱们也可以依葫芦画瓢的把代码写出来了
(1) 引入Emit命名空间
using System.Reflection.Emit;
(2) 首先我们定义一个Main方法,入参无,返回类型void
//定义方法名,返回类型,输入类型 var method = new DynamicMethod("Main", null, Type.EmptyTypes);
(3) 生成IL代码
//生成IL代码 var ilGenerator = method.GetILGenerator(); ilGenerator.Emit(OpCodes.Nop); ilGenerator.Emit(OpCodes.Ldstr,"Hello World!"); ilGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) })); //寻找Console的WriteLine方法 ilGenerator.Emit(OpCodes.Nop); ilGenerator.Emit(OpCodes.Ret);
(4) 创建委托并调用
//创建委托 var helloWorldMethod = method.CreateDelegate(typeof(Action)) as Action; helloWorldMethod.Invoke();
(5)运行,即输出Hello World!
五、小结
既然C#作为一门面向对象的语言,所以首当其冲的我们需要让Emit为我们动态构建类。
废话不多说,首先,我们先来回顾一下C#类的内部由什么东西组成:
(1) 字段-C#类中保存数据的地方,由访问修饰符、类型和名称组成;
(2) 属性-C#类中特有的东西,由访问修饰符、类型、名称和get/set访问器组成,属性的是用来控制类中字段数据的访问,以实现类的封装性;在Java当中写作getXXX()和setXXX(val),C#当中将其变成了属性这种语法糖;
(3) 方法-C#类中对逻辑进行操作的基本单元,由访问修饰符、方法名、泛型参数、入参、出参构成;
(4) 构造器-C#类中一种特殊的方法,该方法是专门用来创建对象的方法,由访问修饰符、与类名相同的方法名、入参构成。
接着,我们再观察C#类本身又具备哪些东西:
(1) 访问修饰符-实现对C#类的访问控制
(2) 继承-C#类可以继承一个父类,并需要实现父类当中所有抽象的方法以及选择实现父类的虚方法,还有就是子类需要调用父类的构造器以实现对象的创建
(3) 实现-C#类可以实现多个接口,并实现接口中的所有方法
(4) 泛型-C#类可以包含泛型参数,此外,类还可以对泛型实现约束
以上就是C#类所具备的一些元素,以下为样例:
public abstract class Bar { public abstract void PrintName();
} public interface IFoo{ public T Name { get; set; } } //继承Bar基类,实现IFoo接口,泛型参数T
public class Foo: Bar, IFoo //构造器 public Foo(T name):base() { _name = name; } //字段 private T _name; //属性 public T Name { get => _name; set => _name = value; } //方法 public override void PrintName() {
//泛型约束
where T : struct {
Console.WriteLine(_name.ToString()); }
}
在探索完了C#类及其定义后,我们要来了解C#的项目结构组成。我们知道C#的一个csproj项目最终会对应生成一个dll文件或者exe文件,这一个文件我们称之为程序集Assembly;而在一个程序集中,我们内部包含和定义了许多命名空间,这些命令空间在C#当中被称为模块Module,而模块正是由一个一个的C#类Type组成。
所以,当我们需要定义C#类时,就必须首先定义Assembly以及Module,如此才能进行下一步工作。
二、IL概览
有了以上的对C#类的解读和IL的解读,我们知道了C#类本身所需要哪些元素,我们就开始根据这些元素来开始编写Emit代码了。这里的代码量会比较大,请读者慢慢阅读,也可以参照以上我写的类生成il代码进行比对。
在Emit当中所有创建类型的帮助类均以Builder结尾,从下表中我们可以看的非常清楚
| 元素中文 | 元素名称 | 对应Emit构建器名称 |
|---|---|---|
| 程序集 | Assembly | AssemblyBuilder |
| 模块 | Module | ModuleBuilder |
| 类 | Type | TypeBuilder |
| 构造器 | Constructor | ConstructorBuilder |
| 属性 | Property | PropertyBuilder |
| 字段 | Field | FieldBuilder |
| 方法 | Method | MethodBuilder |
由于创建类需要从Assembly开始创建,所以我们的入口是AssemblyBuilder
(1) 首先,我们先引入命名空间,我们以上节Foo类为样例进行编写
using System.Reflection.Emit;
(2) 获取基类和接口的类型
var barType = typeof(Bar); var interfaceType = typeof(IFoo<>);
(3) 定义Foo类型,我们可以看到在定义类之前我们需要创建Assembly和Module
//定义类 var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Edwin.Blog.Emit"), AssemblyBuilderAccess.Run); var moduleBuilder = assemblyBuilder.DefineDynamicModule("Edwin.Blog.Emit"); var typeBuilder = moduleBuilder.DefineType("Foo", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit);
(4) 定义泛型参数T,并添加约束
//定义泛型参数 var genericTypeBuilder = typeBuilder.DefineGenericParameters("T")[0]; //设置泛型约束 genericTypeBuilder.SetGenericParameterAttributes(GenericParameterAttributes.NotNullableValueTypeConstraint);
(5) 继承和实现接口,注意当实现类的泛型参数需传递给接口时,需要将泛型接口添加泛型参数后再调用AddInterfaceImplementation方法
//继承基类 typeBuilder.SetParent(barType); //实现接口 typeBuilder.AddInterfaceImplementation(interfaceType.MakeGenericType(genericTypeBuilder));
(6) 定义字段,因为字段在构造器值需要使用,故先创建
//定义字段 var fieldBuilder = typeBuilder.DefineField("_name", genericTypeBuilder, FieldAttributes.Private);
(7) 定义构造器,并编写内部逻辑
//定义构造器 var ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.Standard, new Type[] { genericTypeBuilder }); var ctorIL = ctorBuilder.GetILGenerator(); //Ldarg_0在实例方法中表示this,在静态方法中表示第一个参数 ctorIL.Emit(OpCodes.Ldarg_0); ctorIL.Emit(OpCodes.Ldarg_1); //为field赋值 ctorIL.Emit(OpCodes.Stfld, fieldBuilder); ctorIL.Emit(OpCodes.Ret);
(8) 定义Name属性
//定义属性 var propertyBuilder = typeBuilder.DefineProperty("Name", PropertyAttributes.None, genericTypeBuilder, Type.EmptyTypes);
(9) 编写Name属性的get/set访问器
//定义get方法 var getMethodBuilder = typeBuilder.DefineMethod("get_Name", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.SpecialName | MethodAttributes.Virtual, CallingConventions.Standard, genericTypeBuilder, Type.EmptyTypes); var getIL = getMethodBuilder.GetILGenerator(); getIL.Emit(OpCodes.Ldarg_0); getIL.Emit(OpCodes.Ldfld, fieldBuilder); getIL.Emit(OpCodes.Ret); typeBuilder.DefineMethodOverride(getMethodBuilder, interfaceType.GetProperty("Name").GetGetMethod()); //实现对接口方法的重载 propertyBuilder.SetGetMethod(getMethodBuilder); //设置为属性的get方法 //定义set方法 var setMethodBuilder = typeBuilder.DefineMethod("set_Name", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.SpecialName | MethodAttributes.Virtual, CallingConventions.Standard, null, new Type[] { genericTypeBuilder }); var setIL = setMethodBuilder.GetILGenerator(); setIL.Emit(OpCodes.Ldarg_0); setIL.Emit(OpCodes.Ldarg_1); setIL.Emit(OpCodes.Stfld, fieldBuilder); setIL.Emit(OpCodes.Ret); typeBuilder.DefineMethodOverride(setMethodBuilder, interfaceType.GetProperty("Name").GetSetMethod()); //实现对接口方法的重载
propertyBuilder.SetSetMethod(setMethodBuilder); //设置为属性的set方法
(10) 定义并实现PrintName方法
//定义方法 var printMethodBuilder = typeBuilder.DefineMethod("PrintName", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, CallingConventions.Standard, null, Type.EmptyTypes); var printIL = printMethodBuilder.GetILGenerator(); printIL.Emit(OpCodes.Ldarg_0); printIL.Emit(OpCodes.Ldflda, fieldBuilder); printIL.Emit(OpCodes.Constrained, genericTypeBuilder); printIL.Emit(OpCodes.Callvirt, typeof(object).GetMethod("ToString", Type.EmptyTypes)); printIL.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) })); printIL.Emit(OpCodes.Ret); //实现对基类方法的重载 typeBuilder.DefineMethodOverride(printMethodBuilder, barType.GetMethod("PrintName", Type.EmptyTypes));
(11) 创建类
var type = typeBuilder.CreateType(); //netstandard中请使用CreateTypeInfo().AsType()
(12) 调用
var obj = Activator.CreateInstance(type.MakeGenericType(typeof(DateTime)), DateTime.Now); (obj as Bar).PrintName(); Console.WriteLine((obj as IFoo).Name);
四、应用
对于Emit,确实初学者会对其感到复杂和难以学习,但是只要搞懂其中的原理,其实最终就是C#和.NET语言的本质所在,在学习Emit的同时,也是在锻炼你的基本功是否扎实,你是否对这门语言精通,是否有各种简化代码的应用。
保持学习,勇于实践;Write Less,Do More;作者之后还会继续.NET高级特性系列,感谢阅读!
出处:https://www.cnblogs.com/billming/p/emit-study-class.html
=======================================================================================
.NET高级特性-Emit(2.1)字段
在上篇blog写完的几天后,有读者反映写的过于复杂,导致无法有效的进行实践;博主在考虑到园子里程序员水平高低不一致的情况,所以打算放慢脚步,对类的一些内容进行详细的讲解,顺带的会写一些笔者所遇到过的Emit的坑以及如何使用Emit来为我们的工作减负,毕竟,知识用到实践当中才有其因有的价值。博主在文末也会将样例上传github,方便大家实践。
首先,照例我先把我之前写的博文链接上来,方便大家阅读
《》
《》
一、什么是字段
讲完了什么是字段,以及一些容易掉入的C#概念误区,我没开始来使用Emit创建字段定义,由于字段只可能是类的一部分,故所以需要使用TypeBuilder来创建字段,对Emit不熟悉的读者可以查看博主的前两篇文章,里面概述了Emit所使用的一些类的定义。
好,咱们开始写代码,首先,我们先给出我们要最终生成的结果:
public class UserField { public static readonly string TokenPrefix = "Bearer"; public UserField() { id = Guid.NewGuid().ToString("N"); } public readonly string id; public string userName; private string passwordHash = "123456"; public string GetPasswodHash() { return passwordHash; } public void SetPassword(string password) { passwordHash = password; } }
我们首先忽略掉类的构造器与方法,我们当前只关注字段的定义,我们可以看到,字段可以由四部分组成:
(1)字段的修饰符-访问修饰符定义了字段的一些特性,如public/private/protected表示访问级别;readonly表示了字段是否可以被外部写入;static表示该字段的归属,是属于对象还是属于类。
(2)字段的类型-字段的类型定义了该字段是由什么数据类型,由此计算机才可以确定该字段在计算机中所使用的内存空间,进而知晓一个对象需要分配多少内存空间才能将数据装入
(3)字段的名称-字段的名称用来表述该字段在该对象/类中所表达的含义,让程序员能理解该字段所存储的数据在现实世界的表述
(4)字段的默认值-字段在类初始化后一定会拥有一个默认值,除了在构造器中或者字段后给予的默认值之外,其它未赋值的字段均使用default填充该字段,当然,不同的字段类型default给予的值也会不一样,对于引用类型会给予null值,对于结构体类型会使用默认构造器,对于基本值类型,会赋予0值,对于枚举,也会赋予0值;这个博主会在之后讲解Emit变量与常量当中会讲解到
好,开始撸代码,第一步当然是要引入我们的主角-Emit类库,而且由于一些枚举特性存放在反射类库中,我们也要将其引入
using System.Reflection.Emit; using System.Reflection;
第二步,创建类,若对创建类的过程不清楚可以阅读我的博文《》,里面详细介绍了类的定义及项目的结构组成
var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Edwin.Blog.Emit"), AssemblyBuilderAccess.Run); var moduleBuilder = asmBuilder.DefineDynamicModule("Edwin.Blog.Emit"); var typeBuilder = moduleBuilder.DefineType("UserField", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.BeforeFieldInit);
第三步,首先创建静态字段TokenPrefix
//第一个变量表示字段名称,第二个变量表示字段的类型,第三个变量表示字段的特性(修饰符)为public readonly static var tokenPrefixBuilder = typeBuilder.DefineField("TokenPrefix", typeof(string), FieldAttributes.Public | FieldAttributes.InitOnly | FieldAttributes.Static);
第四步,同第三步,创建其余非静态字段
var idBuilder = typeBuilder.DefineField("id", typeof(string), FieldAttributes.Public | FieldAttributes.InitOnly); var userNameBuilder = typeBuilder.DefineField("userName", typeof(string), FieldAttributes.Public); var passwordHashBuilder = typeBuilder.DefineField("passwordHash", typeof(string), FieldAttributes.Private);
这样我们的字段就定义好了。
ok,相信很多读者都有疑问,我这怎么没写默认值啊,你看字段TokenPrefix都有字段携带着啊,你怎么就把它丢掉了呢?别急,其实在字段后面写默认值也是C#语言的语法糖,我会在下一节进行讲述。
三、字段的操作
在编写C#时,一定要小心C#自带的语法糖产生错误认知,看穿语法糖的本质,你对这门语言的理解就更加深入,对你了解其它语言也有类似的帮助,毕竟即使编程语言在不断的涌现和发展,你也能把握其最本质的、不变的东西,就像算法与数据结构一样是软件的灵魂一样。
下一篇,博主将详细介绍C#中最特殊的东西-属性,感谢阅读,以下为github样例地址
https://github.com/MJEdwin/edwin-blog-sample/blob/master/Edwin.Blog.Sample/Field/UserEmit.cs
出处:https://www.cnblogs.com/billming/p/emit-study-field.html
=======================================================================================
.NET高级特性-Emit(2.2)属性
关于Emit的博客已经进入第四篇,在读本篇博文之前,我希望读者能先仔细回顾博主之前所编写的关于Emit的博文,从该篇博文开始,我们就可以真正的使用Emit,并把知识转化为实战,我也会把之前的博文链接放在下方,以方便读者阅读,大家也可以将自己的疑问或者指正写在评论当中,博主会积极进行回复。
ok,今天我们继续来探索C#-Emit中关于类的知识和应用,今天我们要来探索和挖掘关于C#属性的二三事,并且我们要开始使用Emit中关于类、字段和属性开启我们的第一个应用-动态创建匿名类
一、什么是属性?
简单讲完了属性是什么以及属性的本质,我们就要来简要说说IL中的属性,因为Emit当中最终编写的还是IL代码。在IL当中,属性或者自动属性它的原本面貌就会被还原,下面的样例看的就清清楚楚。
首先,我们先定义一个Blog类,里面包含两个属性-Title和Content,表示标题和内容
public class Blog { public string Title { get; set; } public string Content { get; set; } }
接着,使用ildasm工具查看IL代码,ildasm工具博主有在《》中讲到如何使用,我们可以看到仅仅一句话定义Title属性的话,C#为我生成四个东西,分别是
- Title字段
- get_Title方法
- set_Title方法
- Title属性
我们双击查看Title属性,可以看到它的get和set直接链接向get_Title方法和set_Title方法
之后,我们来观察下get_TItle方法和set_Title方法,结合上一章《》对字段操作,我们很明显的看到,set_Title方法实现了对字段的赋值,而get_Title方法也正好对应了字段的取值
这就是在IL中呈现的属性的真正样貌,有了IL的理解,我们就能开始我们的Emit之旅了。
三、属性的定义
请读者思考,如果我定义一个方法,方法中传入类所需要的属性的名称和它对应的类型,我们是不是就可以根据上述创建Blog的方式来创建一个只包含属性和字段的类,这样的类不就是我们C#当中所说的匿名类了吗?
想想,我们平常开发当中什么时候使用匿名类居多?博主告诉你,没错就是Mapper,C#当中匿名类存在的意义就是可以实现实体对象到匿名对象的映射,使用最广泛的就是在Linq当中,那么如果我们用Emit来创建匿名类,再在Linq中完成实体类到匿名类的映射,我们我就可以动态DynamicLinq了吗?
由于篇幅原因以及其中包含了表达式树的原因,故博主就不将代码放在博文当中,有兴趣的读者可以查看我的github了解实现,博主将动态Select的流程图画在下方,知识丰富的小伙伴也可以自行实现。
五、小结
本章讲解了属性是什么,Emit如何编写属性,以及属性最重要的一个应用-创建匿名类;不积跬步无以至千里,不积小流无以成江海,作为身处软件行业的我们来说,更需要这一份持之以恒的积累,只有不断的积累-思考-积累-思考,才能从量变完成质变,写出更加优秀的代码和软件。
博主将继续更新.NET高级特性系列,感谢阅读!!!
出处:https://www.cnblogs.com/billming/p/emit-study-property.html
=======================================================================================