zoukankan      html  css  js  c++  java
  • .NET高级特性-Emit(2)类的定义

      在上一篇博文发了一天左右的时间,就收到了博客园许多读者的评论和推荐,非常感谢,我也会及时回复读者的评论。之后我也将继续撰写博文,梳理相关.NET的知识,希望.NET的圈子能越来越大,开发者能了解/深入.NET的本质,将工作做的简单又高效,拒绝重复劳动,拒绝CRUD。

      ok,咱们开始继续Emit的探索。在这之前,我先放一下我往期关于Emit的文章,方便读者阅读。

      《.NET高级特性-Emit(1)

    一、基础知识

      既然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")[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<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高级特性系列,感谢阅读!

  • 相关阅读:
    Objective-c Category(类别)
    协议(porotocol)
    类的通用格式
    objective-c 强大的布尔类型
    C 语言函数指针
    c while 循环
    jQuery的deferred对象详解
    exploring the http Object
    div+css定位position详解
    如何给变量取个简短且无歧义的名字
  • 原文地址:https://www.cnblogs.com/billming/p/emit-study-class.html
Copyright © 2011-2022 走看看