.NET高级特性-Emit(2)类的定义
在上一篇博文发了一天左右的时间,就收到了博客园许多读者的评论和推荐,非常感谢,我也会及时回复读者的评论。之后我也将继续撰写博文,梳理相关.NET的知识,希望.NET的圈子能越来越大,开发者能了解/深入.NET的本质,将工作做的简单又高效,拒绝重复劳动,拒绝CRUD。
ok,咱们开始继续Emit的探索。在这之前,我先放一下我往期关于Emit的文章,方便读者阅读。
一、基础知识
既然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<T>
{
public T Name { get; set; }
}
//继承Bar基类,实现IFoo接口,泛型参数T
public class Foo<T> : Bar, IFoo<T>
//泛型约束
where T : struct
{
//构造器
public Foo(T name):base()
{
_name = name;
} //字段
private T _name;
//属性
public T Name { get => _name; set => _name = value; }
//方法
public override void PrintName()
{
Console.WriteLine(_name.ToString());
}
}
在探索完了C#类及其定义后,我们要来了解C#的项目结构组成。我们知道C#的一个csproj项目最终会对应生成一个dll文件或者exe文件,这一个文件我们称之为程序集Assembly;而在一个程序集中,我们内部包含和定义了许多命名空间,这些命令空间在C#当中被称为模块Module,而模块正是由一个一个的C#类Type组成。
所以,当我们需要定义C#类时,就必须首先定义Assembly以及Module,如此才能进行下一步工作。
二、IL概览
由于Emit实质是通过IL来生成C#代码,故我们可以反向生成,先将写好的目标代码写成cs文件,通过编译器生成dll,再通过ildasm查看IL代码,即可依葫芦画瓢的编写出Emit代码。所以我们来查看以下上节Foo所生成的IL代码。
从上图我们可以很清晰的看到.NET的层级结构,位于树顶层浅蓝色圆点表示一个程序集Assembly,第二层蓝色表示模块Module,在模块下的均为我们所定义的类,类中包含类的泛型参数、继承类信息、实现接口信息,类的内部包含构造器、方法、字段、属性以及它的get/set方法,由此,我们可以开始编写Emit代码了
三、Emit编写
有了以上的对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")[];
//设置泛型约束
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<DateTime>).Name);
四、应用
上面的样例仅供学习只用,无法运用在实际项目当中,那么,Emit构建类在实际项目中我们可以有什么应用,提高我们的编码效率
(1) 动态DTO-当我们需要将实体映射到某个DTO时,可以用动态DTO来代替你手写的DTO,选择你需要的字段回传给前端,或者前端把他想要的字段传给后端
(2) DynamicLinq-我的第一篇博文有个读者提到了表达式树,而linq使用的正是表达式树,当表达式树+Emit时,我们就可以用像SQL或者GraphQL那样的查询语句实现动态查询
(3) 对象合并-我们可以编写实现一个像js当中Object.assign()一样的方法,实现对两个实体的合并
(4) AOP动态代理-AOP的核心就是代理模式,但是与其对应的是需要手写代理类,而Emit就可以帮你动态创建代理类,实现切面编程
(5) ...
五、小结
对于Emit,确实初学者会对其感到复杂和难以学习,但是只要搞懂其中的原理,其实最终就是C#和.NET语言的本质所在,在学习Emit的同时,也是在锻炼你的基本功是否扎实,你是否对这门语言精通,是否有各种简化代码的应用。
保持学习,勇于实践;Write Less,Do More;作者之后还会继续.NET高级特性系列,感谢阅读!
.NET高级特性-Emit(2)类的定义的更多相关文章
- .NET高级特性-Emit(2.1)字段
在上篇blog写完的几天后,有读者反映写的过于复杂,导致无法有效的进行实践:博主在考虑到园子里程序员水平高低不一致的情况,所以打算放慢脚步,对类的一些内容进行详细的讲解,顺带的会写一些笔者所遇到过的E ...
- .NET高级特性-Emit(2.2)属性
关于Emit的博客已经进入第四篇,在读本篇博文之前,我希望读者能先仔细回顾博主之前所编写的关于Emit的博文,从该篇博文开始,我们就可以真正的使用Emit,并把知识转化为实战,我也会把之前的博文链接放 ...
- .NET高级特性-Emit(1)
在这个大数据/云计算/人工智能研发普及的时代,Python的崛起以及Javascript的前后端的侵略,程序员与企业似乎越来越青睐动态语言所带来的便捷性与高效性,即使静态语言在性能,错误检查等方面的优 ...
- Java开发知识之Java类的高级特性,内部类.以及包使用.跟常量关键字
Java开发知识之Java类的高级特性,内部类.以及包使用.跟常量关键字 一丶Java中包的机制 首先包其实就是个文件夹.作用就是管理类. Java中每次定义一个类的时候.通过Java编译之后.都会生 ...
- Python的高级特性8:你真的了解类,对象,实例,方法吗
Python的高级特性1-7系列是本人从Python2过渡3时写下的一些个人见解(不敢说一定对),接下来的系列主要会以类级为主. 类,对象,实例,方法是几个面向对象的几个基本概念,其实我觉得很多人并不 ...
- paip。java 高级特性 类默认方法,匿名方法+多方法连续调用, 常量类型
paip.java 高级特性 类默认方法,匿名方法+多方法连续调用, 常量类型 作者Attilax 艾龙, EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http ...
- JavaScript 类的定义和引用 JavaScript高级培训 自定义对象
在Java语言中,我们可以定义自己的类,并根据这些类创建对象来使用,在Javascript中,我们也可以定义自己的类,例如定义User类.Hashtable类等等. 一,概述 在Java语言中 ...
- No2_5.类的高级特性_Java学习笔记_抽象类和成员内部类
一.抽象类1.所谓抽象类,只声明方法的存在而不去实现它的类:2.抽象类不能被实例化,即不能实现其对象:3.abstract class 类名{ 类体 }4.包含一个或多个抽象方法的类必须声明成抽象类: ...
- 【IOS 开发】Objective - C 面向对象高级特性 - 包装类 | 类处理 | 类别 | 扩展 | 协议 | 委托 | 异常处理 | 反射
一. Objective-C 对象简单处理 1. 包装类 (1) 包装类简介 NSValue 和 NSNumber : -- 通用包装类 NSValue : NSValue 包装单个 short, i ...
随机推荐
- 3. SOFAJRaft源码分析— 是如何进行选举的?
开篇 在上一篇文章当中,我们讲解了NodeImpl在init方法里面会初始化话的动作,选举也是在这个方法里面进行的,这篇文章来从这个方法里详细讲一下选举的过程. 由于我这里介绍的是如何实现的,所以请大 ...
- 百万年薪python之路 -- 并发编程之 多线程 二
1. 死锁现象与递归锁 进程也有死锁与递归锁,进程的死锁和递归锁与线程的死锁递归锁同理. 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因为争夺资源而造成的一种互相等待的现象,在无外力的作用 ...
- webpack知识分享
webpack 4 webpack 四大核心概念: 入口(entry) // 打包入口 输出(output) : 打包后输出的位置配置 loader : loader 让 webpack 能够去处理 ...
- Fiddler抓包工具的基本操作
Fiddler ——位于客户端和服务器端的HTTP代理 代理:客户端所有请求都先经过fiddler,然后转发到相应服务器 服务器端所有相应都先经过fiddler,然后发送到客户端 1. 常用的HTTP ...
- Java基础(五)继承和多态
1.多态 先来看一个例子,其中Employee类是父类,Manager类继承了Employee类: public static void main(String[] args) { // constr ...
- django-URL转换器(四)
接URL匹配那一节. 在book中的urls.py from django.urls import path from . import views urlpatterns = [ path('', ...
- 漏洞靶场--webug4.0安装
官网:https://www.webug.org/ 官方版本里安装视频教程 7.19官网打不开,分享当初存在网盘的[7.1更新] 链接: https://pan.baidu.com/s/1F3658i ...
- 第四十章 POSIX条件变量
条件变量 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中.这种情况就需要用到条件 ...
- Web for pentester_writeup之XML attacks篇
Web for pentester_writeup之XML attacks篇 XML attacks(XML攻击) Example 1 - XML外部实体注入(XXE) Payload http:// ...
- mysql分组和去重同时使用
这是我的数据结构: 这是我的统计SQL