關於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高級特性系列,感謝閱讀!!!