1. 程式人生 > >.NET高階特性-Emit(2.2)屬性

.NET高階特性-Emit(2.2)屬性

  關於Emit的部落格已經進入第四篇,在讀本篇博文之前,我希望讀者能先仔細回顧博主之前所編寫的關於Emit的博文,從該篇博文開始,我們就可以真正的使用Emit,並把知識轉化為實戰,我也會把之前的博文連結放在下方,以方便讀者閱讀,大家也可以將自己的疑問或者指正寫在評論當中,博主會積極進行回覆。

  • .NET高階特性-Emit(1)
  • .NET高階特性-Emit(2)類的定義
    • .NET高階特性-Emit(2.1)欄位

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