.NET高级特性-Emit(2.2)属性

2019-12-01 来源: BillMing 发布在  https://www.cnblogs.com/billming/p/emit-study-property.html

  关于Emit的博客已经进入第四篇,在读本篇博文之前,我希望读者能先仔细回顾博主之前所编写的关于Emit的博文,从该篇博文开始,我们就可以真正的使用Emit,并把知识转化为实战,我也会把之前的博文链接放在下方,以方便读者阅读,大家也可以将自己的疑问或者指正写在评论当中,博主会积极进行回复。

  ok,今天我们继续来探索C#-Emit中关于类的知识和应用,今天我们要来探索和挖掘关于C#属性的二三事,并且我们要开始使用Emit中关于类、字段和属性开启我们的第一个应用-动态创建匿名类 

一、什么是属性?

  属性-C#中让人既爱又恨的东西,爱的是C#当中因为有了属性,.NET开发者只需要一句话就可以完成对类的封装,根本不需要像其它语言写这么多东西,我们可以用java来比较一下

  在C#当中我们定义一个实体属性

public string Title { get; set; }

  在Java当中我们就需要这样定义

private String title;

public String getTitle()
{
    return title;
}

public void setTitle(String value)
{
    title = value;
}

  在C#当中简简单单的一句话在Java当中就需要写一个字段将两个方法,当然我不是在贬低Java,只是表明Java没有在语法上为开发者提供便利,当然这些年Java的语法也在逐渐完善,从Java8开始逐渐加入了推断类型var/匿名委托等等优秀的语法。

  扯的有点远了,当然C#中使用属性也有它的问题,首先是许多入门级的程序员把属性当成字段进行泛滥的使用,造成了C#类失去了封装性,没有了封装,有可能就会造成致命的漏洞,所以请刚入门的程序员请慎重使用属性,属性虽然好但是不要滥用,在你对属性不熟悉的时候,尤其要处理好它的set访问器,或者抛弃属性使用以下最原始的方法进行编写。

private string title;

public string GetTitle()
{
    return title;
}

public void SetTitle(string value)
{
    title = value;
}

  ok,其实在上面与Java的比较当中我们其实已经知道了属性是什么了,属性是对类中一类特殊方法的语法糖,这一类方法的功能是负责对字段的读取和设置,称之为get/set访问器,get方法用于获取字段的值,而set方法是对传入的值对字段进行赋值,当然,如何赋值和取值,就取决于你方法怎么写了。

  那么有的读者就会有疑问,既然属性只是对于get/set访问器的语法糖,那么对应的字段跑哪里去了呢,其实这里面还运用了一种叫做自动属性的语法糖,这是在C#5.0之后增加的一种语法糖,对它详细的讲解可以查看我的博文《.NET高级特性-Emit(2.1)字段》,文章中详细说明了C#如何将最终的字段省略的全过程。

二、IL中的属性

  简单讲完了属性是什么以及属性的本质,我们就要来简要说说IL中的属性,因为Emit当中最终编写的还是IL代码。在IL当中,属性或者自动属性它的原本面貌就会被还原,下面的样例看的就清清楚楚。

  首先,我们先定义一个Blog类,里面包含两个属性-Title和Content,表示标题和内容

    public class Blog
    {
        public string Title { get; set; }

        public string Content { get; set; }
    }

  接着,使用ildasm工具查看IL代码,ildasm工具博主有在《.NET高级特性-Emit(1)》中讲到如何使用,我们可以看到仅仅一句话定义Title属性的话,C#为我生成四个东西,分别是

  • Title字段
  • get_Title方法
  • set_Title方法
  • Title属性

  我们双击查看Title属性,可以看到它的get和set直接链接向get_Title方法和set_Title方法

 

  之后,我们来观察下get_TItle方法和set_Title方法,结合上一章《.NET高级特性-Emit(2.1)字段》对字段操作,我们很明显的看到,set_Title方法实现了对字段的赋值,而get_Title方法也正好对应了字段的取值

  这就是在IL中呈现的属性的真正样貌,有了IL的理解,我们就能开始我们的Emit之旅了。

三、属性的定义

  属性的定义其实很简单,属性真正的难点是在于如何编写get/set访问器,因为这才是属性的核心逻辑,而且对于自动属性来说我们需要定义字段/get访问器/set访问器和属性本身,所以博主打算用一个方法来实现自动属性的生成,已完成这一个过程的复用

  首先,我们来看一下方法定义,博主使用了扩展方法来为TypeBuilder扩展一个定义自动属性的方法,该方法只需要属性名称和类型,即可创建自动属性需要定义的字段/get访问器/set访问器和属性本身,工欲善其事必先利其器,有了这个方法我们就能快速的创建自动属性

public static PropertyBuilder DefineAutomaticProperty(this TypeBuilder typeBuilder, string propertyName, Type propertyType)
{
    //do something
}

  (1)然后,我们定义属性的字段,由于是自动属性,所以字段的类型与属性类型相同,名称博主采用下划线+属性小写的方式定义

            var fieldBuilder = typeBuilder.DefineField("_" + propertyName.ToLower(), propertyType, FieldAttributes.Private);

  (2)之后,我们定义属性,这个时候的属性是没有任何get/set访问器的

            var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, Type.EmptyTypes);

  (3)在定义完属性之后,我们开始编写属性的get方法,get方法的内容是读取字段值并返回,如何编写可以参考我的文章《.NET高级特性-Emit(2.1)字段》中字段操作一节

            //定义Get方法,返回属性类型,入参无
            var getMethodBuilder = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, propertyType, Type.EmptyTypes);
            var getIL = getMethodBuilder.GetILGenerator();
            getIL.Emit(OpCodes.Ldarg_0);
            //将字段放入栈顶
            getIL.Emit(OpCodes.Ldfld, fieldBuilder);
            getIL.Emit(OpCodes.Ret);

  (4)之后,我们同样定义属性的set方法,内容为读取第一个参数并保存到字段,emit含义同样可以参考上一步get方法的文章

            //定义Set方法,返回void,入参一个,类型为属性类型
            var setMethodBuilder = typeBuilder.DefineMethod("set_" + propertyName, MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName, null, new Type[] { propertyType });
            var setIL = setMethodBuilder.GetILGenerator();
            setIL.Emit(OpCodes.Ldarg_0);
            //将第一个参数放入栈顶
            setIL.Emit(OpCodes.Ldarg_1);
            //将栈顶元素弹出并保存到字段
            setIL.Emit(OpCodes.Stfld, fieldBuilder);
            setIL.Emit(OpCodes.Ret);

  (5)最后,我们将get和set方法设置为属性的get和set

            propertyBuilder.SetGetMethod(getMethodBuilder);
            propertyBuilder.SetSetMethod(setMethodBuilder);

  (6)返回属性,我们的定义自动属性方法就完成了,完整代码用户可以查看我的github:

            return propertyBuilder;

  在定义自动属性方法中,我们定义了字段/属性/get访问器与set访问器,这样,我们定义Blog类就非常的简单

  首先,我们只要先定义Blog类

            var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("Edwin.Blog.Emit"), AssemblyBuilderAccess.Run);
            var moduleBuilder = asmBuilder.DefineDynamicModule("Edwin.Blog.Emit");
            var typeBuilder = moduleBuilder.DefineType("Blog", TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.BeforeFieldInit);

  然后直接使用上面我们定义的扩展方法来定义自动属性

            typeBuilder.DefineAutomaticProperty("Title", typeof(string));
            typeBuilder.DefineAutomaticProperty("Content", typeof(string));

  最后创建类型,就完成了我们对Blog类的创建

            typeBuilder.CreateTypeInfo().AsType();

  最后创建并对属性赋值

            dynamic user = Activator.CreateInstance(type);
            user.Title = "Emit高级特性-属性";
            user.Content = "xxx";

  即可在调试窗口看到如下结果

  样例github地址:https://github.com/MJEdwin/edwin-blog-sample/blob/master/Edwin.Blog.Sample/Property/BlogEmit.cs

四、属性的应用-匿名类

  请读者思考,如果我定义一个方法,方法中传入类所需要的属性的名称和它对应的类型,我们是不是就可以根据上述创建Blog的方式来创建一个只包含属性和字段的类,这样的类不就是我们C#当中所说的匿名类了吗?

  想想,我们平常开发当中什么时候使用匿名类居多?博主告诉你,没错就是Mapper,C#当中匿名类存在的意义就是可以实现实体对象到匿名对象的映射,使用最广泛的就是在Linq当中,那么如果我们用Emit来创建匿名类,再在Linq中完成实体类到匿名类的映射,我们我就可以动态DynamicLinq了吗?

  由于篇幅原因以及其中包含了表达式树的原因,故博主就不将代码放在博文当中,有兴趣的读者可以查看我的github了解实现,博主将动态Select的流程图画在下方,知识丰富的小伙伴也可以自行实现。

五、小结

  本章讲解了属性是什么,Emit如何编写属性,以及属性最重要的一个应用-创建匿名类;不积跬步无以至千里,不积小流无以成江海,作为身处软件行业的我们来说,更需要这一份持之以恒的积累,只有不断的积累-思考-积累-思考,才能从量变完成质变,写出更加优秀的代码和软件。

  博主将继续更新.NET高级特性系列,感谢阅读!!!

相关文章