.NET高级特性-Emit(2.2)属性
关于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高级特性系列,感谢阅读!!!