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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM