《必須知道的.net》讀書筆記(二)

 

第二部分本質——.Net深入淺出

第1章一切從IL開始

1引言

《The C programming Language》

2Hello World開始

3IL體驗中心

應用 ILDasm.exe 反編譯工具

編譯後的 IL 結構中,包含了 MANIFEST 和 HelloWorld 類,其中 MANIFEST 是個附加信息列表,主要包含了程序集的一些屬性。

3.1MANIFEST清單分析

.assembly 指令用於定義編譯目標或者加載外部庫。.assembly extern mscorlib 表示外部加載了外部核心庫 mscorlib

我們知道 mscorlib.dll 程序集定義 managed code 依賴的核心數據類型,屬於必須加載項。

在外部指令中還會指明瞭引用版本(.ver);應用程序實際公鑰標記(.publickeytoken),公鑰Token 是 SHA1 哈希碼的低 8 位字節的反序

HelloWorld 程序集中包括了.hash algorithm 指令,表示實現安全性所使用的哈希算法,系統缺省爲 0x00008004,表明爲 SHA1 算法;.ver 則表示了 HelloWorld 程序集的版本號;

程序集由模塊組成, .module 爲程序集指令,表明定義的模塊的元數據,以指定當前模塊。

其他的指令還有:imagebase 爲影像基地址;.file alignment 爲文件對齊數值;.subsystem 爲連接系統類型,0x0003 表示從控制檯運行;.corflags 爲設置運行庫頭文件標誌,默認爲1。

3.2HelloWorld類分析

.class public auto ansi beforefieldinit HelloWorld

       extends [mscorlib]System.Object

{

} // end of class HelloWorld

     .class 表明了 HelloWorld 是一個 public 類,該類繼承自外部程序集 mscorlib 的 System.Object 類。 

     public 爲訪問控制權限,這點很容易理解。 

     auto 表明程序加載時內存的佈局是由 CLR 決定的,而不是程序本身 

     ansi 屬性則爲了在沒有被管理和被管理代碼間實現無縫轉換。沒有被管理的代碼,指的是沒有運行在 CLR 運行庫之上的代碼。 

     beforefieldinit 屬性爲 HelloWorld 提供了一個附加信息,用於標記運行庫可以在任何時候執行類型構造函數方法,只要該方法在第一次訪問其靜態字段之前執行即可。如果沒有 beforefieldinit 則運行庫必須在某個精確時間執行類型構造函數方法,從而影響性能優化。

.method public hidebysig specialname rtspecialname 

        instance void  .ctor() cil managed

{

  // 代碼大小       7 (0x7)

  .maxstack  8

  IL_0000:  ldarg.0

  IL_0001:  call       instance void [mscorlib]System.Object::.ctor()

  IL_0006:  ret

} // end of method HelloWorld::.ctor

     cil managed 說明方法體中爲 IL 代碼,指示編譯器編譯爲託管代碼。 

     .maxstack 表明執行構造函數.ctor 期間的評估堆棧(Evaluation Stack)可容納數據項的最大個數。關於評估堆棧,其用於保存方法所需變量的值,並在方法執行結束時清空,或者存儲一個返回值。

     IL_0000,是一個標記代碼行開頭,一般來說,IL_之前的部分爲變量的聲明和初始化。

     ldarg.0 表示裝載第一個成員參數,在實例方法中指的是當前實例的引用,該引用將用於在基類構造函數中調用。

     call 指令一般用於調用靜態方法,因爲靜態方法是在編譯期指定的,而在此調用的是構造函數.ctor()也是在編譯期指定的;而另一個指令 callvirt 則表示調用實例方法,它的調用過程有異於 call,函數的調用是在運行時確定的,首先會檢查被調用函數是否爲虛函數,如果不是就直接調用,如果是則向下檢查子類是否有重寫,如果有就調用重寫實現,如果沒有還調用原來的函數,依次類推直到找到最新的重寫實現。

     ret 表示執行完畢,返回。

.method public hidebysig static void  Main() cil managed

{

  .entrypoint

  // 代碼大小       11 (0xb)

  .maxstack  8

  IL_0000:  ldstr      "Hello, world."

  IL_0005:  call       void [mscorlib]System.Console::WriteLine(string)

  IL_000a:  ret

} // end of method HelloWorld::Main

.entrypoint 指令表明了 CLR 加載程序 HelloWorld.exe 時,是首先從.entrypoint 方法開始執行的,也就是表明 Main 方法將作爲程序的入口函數。每個託管程序必須有並且只有一個入口點。這區別於將 Main 函數作爲程序入口標誌。

ldstr 指令表示將字符串壓棧,"Hello, world."字符串將被移到 stack 頂部。CLR 通過從元數據表中獲得文字常量來構造 string 對象。

hidebysig 屬性用於表示如果當前類作爲父類時,類中的方法不會被子類繼承,因此 HelloWorld子類中不會看到 Main 方法。

3.3迴歸簡潔

 

第2章認識IL代碼——從基礎到工具

1引言

《Applied Microsoft .NET Framework Programming》、《.NET 本質論》

2使用工具

ILadsm.exe 和 reflector.exe

3分析結構

4解析常用命令

下載[MSIL 指令速查手冊]

4.1newobj和initobj

newobj 用於分配和初始化對象;而 initobj 用於初始化值類型。

newobj 在初始化過程中會調用構造函數;而 initobj 不會調用構造函數,而是直接對實例置空。

newobj 有內存分配的過程;而 initobj 則只完成數據初始化操作。

Initobj 還用於完成設定對指定存儲單元的指針置空(null)。這一操作雖不常見,但是應該引起注意。

Newarr 指令用來創建一維從零起始的數組;而多維或非從零起始的一維數組,則仍由 newobj 指令創建。

String 類型的創建由 ldstr 指令來完成。

4.2call,callvirt,calli

       call 使用靜態調度,也就是根據引用類型的靜態類型來調度方法。

       callvirt 使用虛擬調度,也就是根據引用類型的動態類型來調度方法;

       calli 又稱間接調用,是通過函數指針來執行方法調用;對應的直接調用當然就是前面的:call 和 callvirt。

 

第3章品味類型

3.1品味類型——從通用類型系統開始

1引言

CLI>CTS>CLS

2基本概念

通用類型系統定義瞭如何在運行庫中聲明、使用和管理類型,同時也是運行庫支持跨語言集成的一個重要組成部分。

3位置和關係

CLI>CTS>CLS

4通用規則

類型轉換

可以給類型創建別名

一個對象獲得類型的辦法是:obj.GetType()。

Typeof 操作符

可以使用 CLSCompliantAttribute將程序集、模塊、類型和成員標記爲符合 CLS 或不符合 CLS。

IL 中使用/checked+開關來進行基元類型的溢出檢查,在 C#中實現這一功能的是 checked 和 unchecked 操作符。

3.2品味類型——值類型和引用類型

1內存有理

內存機制

值類型引用類型嵌套

2規則無邊

string 類型是個特殊的引用類型,每次對 string 的改變都會在託管堆中產生一個新的 string 變量。

通常可以使用 Type.IsValueType 來判斷一個變量的類型是否爲值類型。

.NET 中以操作符 ref 和 out 來標識值類型按引用類型方式傳遞。

值類型與引用類型之間的轉換過程稱爲裝箱與拆箱。

sizeof()運算符用於獲取值類型的大小,但是不適用於引用類型。

值類型使用 new 操作符完成初始化。

引用類型在性能上欠於值類型,,處理數據較小的情況時,應該優先考慮值類型。

值類型都繼承自 System.ValueType,而 System.ValueType 又繼承自 System.Object。

值類型不具有多態性;而引用類型有多態性。

值類型變量不可爲 null 值,值類型都會自行初始化爲 0 值;

值類型有兩種表示:裝箱與拆箱;引用類型只有裝箱一種形式。

 

結構簡單,不必多態的情況下,值類型是較好的選擇;類型的性質不表現出行爲時;數據較小的場合。

隱式顯式轉換

static 訪問修飾符 轉換修飾符 operator 類型(參數表)

3應用征途

執行分析,內存分析,IL分析

3.3參數之惑——傳遞的藝術

1引言

按值傳遞與按引用傳遞,ref和out,param修飾符

2參數基礎論

行參和實參

3傳遞的基礎

泛型類型參數和可變數目參數

param 關鍵字的實質是:param 是定製特性 ParamArrayAttribute 的縮寫。

param 修飾的參數必須爲一維數組。

param 必須在參數列表的最後一個,並且只能使用一次。

4傳遞的藝術

值類型實例傳遞的是該值類型實例的一個拷貝;簡單的說對象作爲參數傳遞時,執行的是對對象地址的拷貝,操作的是該拷貝地址。

按引用傳遞,傳遞的不是參數本身的值,而是參數的地址。如果參數爲值類型,則傳遞的是該值類型的地址;如果參數爲引用類型,則傳遞的是對象引用的地址。

不管是值類型還是引用類型,按引用傳遞必須以 ref 或者 out 關鍵字來修飾,方法定義和方法調用必須同時顯示的使用 ref 或者 out。CLR允許通過 out 或者 ref 參數來重載方法。

不管參數本身是值類型還是引用類型,按引用傳遞時,傳遞的是參數的地址,也就是實例的指針。

 

第4章內存天下

4.1內存管理概要

4.1.1引言

l  對象創建時的內存分配。

l  垃圾回收。

l  非託管資源釋放。

數據庫鏈接、文件句柄、COM 對象等,仍然需要開發者自行清理。

4.1.2內存管理概要

4.2對象創建始末

4.2.1引言

內存分配和初始化

4.2.2內存分配

l  newobj,用於創建引用類型對象。

l  ldstr,用於創建 string 類型對象。

l  newarr,用於分配新的數組對象。

l  box,在值類型轉換爲引用類型對象時,將值類型字段拷貝到託管堆上發生的內存分配。

4.3垃圾回收

—  .NET 垃圾回收機制

— 非託管資源的清理

那些不可達對象則被認爲是可回收對象

垃圾收集器週期性的執行內存清理工作,一般在以下情況出現時垃圾收集器將會啓動:

(1)內存不足溢出時,更確切地應該說是第 0 代對象充滿時。

(2)調用 GC.Collect 方法強制執行垃圾回收。

(3)Windows 報告內存不足時,CLR 將強制執行垃圾回收。

(4)CLR 卸載 AppDomain 時,GC 將對所有代齡的對象執行垃圾回收。

(5)其他情況,例如物理內存不足,超出短期存活代的內存段門限,運行主機拒絕分配內存等等。

內存釋放之前 GC 會首先檢查終止化鏈表中是否有記錄來決定在釋放內存之前執行非託管資源的清理工作,然後才執行內存釋放。

GC 在垃圾回收之後,堆上將出現多個被收集對象的“空洞”,爲避免託管堆的內存碎片,會重新分配內存,壓縮託管堆。CLR 垃圾收集器使用了 Generation 的概念來提升性能,還有其他一些優化策略,如併發收集、大對象策略等,來減少垃圾收集對性能的影響。

CLR 提供了兩種收集器:工作站垃圾收集器(Workstation GC,包含在 mscorwks.dll)和服務器垃圾收集器(Server GC,包含在 mscorsvr.dll)。

在 CLR 加載到進程時,可以通過 CorBindToRuntimeEx()函數來選擇執行哪種收集器,選擇合適的收集器也是有效、高效管理的關鍵。

垃圾收集器將託管堆中的對象分爲三代,分別爲:0、1 和 2。

垃圾收集器總是首先收集第 0 代的不可達對象內存。代齡機制在垃圾回收中的性能優化作用。

僅當第 0 代對象釋放的內存不足以創建新的對象,同時 1 代對象的體積也超出了容量闕值時,垃圾收集器將同時對0 代和 1 代對象進行垃圾回收。

1非託管資源清理

主要有兩種方式:Finalize 方法和 Dispose 方法

在繼承鏈中所有實例將遞歸調用 base.Finalize 方法,也就是意味調用終結器釋放資源時,將釋放所有的資源,包括父類對象引用的資源。

對於重寫了 Finalize 方法的類型來說,可以通過 GC. SuppressFinalize 來免除終結。

l  終止化操作的時間無法控制,執行順序也不能保證。因此,在資源清理上不夠靈活,也可能由於執行順序的不確定而訪問已經執行了清理的對象。

l  Finalize 方法會極大地損傷性能,GC 使用一個終止化隊列的內部結構來跟蹤具有 Finalize 方法的對象。

l  重寫了 Finalize 方法的類型對象,其引用類型對象的代齡將被提升,從而帶來內存壓力。

l  Finalize 方法在某些情況下可能不被執行,例如可能某個終結器被無限期的阻止,則其他終結器得不到調用。

對於 Finalize 方法,有以下規則值得總結:

l  在 C#中無法顯示的重寫 Finalize 方法,只能通過析構函數語法形式來實現。

l  struct 中不允許定義析構函數,只有 class 中才可以,並且只能有一個。

l  Finalize 方法不能被繼承或重載。

l  析構函數不能加任何修飾符,不能帶參數,也不能被顯示調用。

l  執行垃圾回收之前系統會自動執行終止化操作。

l  Finalize 方法中,可以實現使得被清理對象復活的機制,不過這種操作相當危險。

 

實現 Dispose 模式的典型操作中,有幾點說明:

l  Dispose 方法中,應該使用 GC. SuppressFinalize 防止 GC 調用 Finalize 方法,因爲顯式調用 Dispose 顯然是較佳選擇。

l  公有 Dispose 方法不能實現爲虛方法,以禁止在派生類中重寫。

l  在該模式中,公有 Dispose 方法通過調用重載虛方法 Dispose(bool disposing)方法來實現,具體的資源清理操作實現於虛方法中。兩種策略的區別是:disposing 參數爲真時,Dispose 方法由用戶代碼調用,可釋放託管或者非託管資源;disposing 參數爲假時,Dispose 方法由 Finalize 調用,並且只能釋放非託管資源。

l  disposed 字段,保證了兩次調用 Dispose 方法不會拋出異常,值得推薦。

l  派生類中實現 Dispose 模式,應該重寫基類的受保護 Dispose 方法,並且通過 base 調用基類的Dispose 方法,以確保釋放繼承鏈上所有對象的引用資源,在整個繼承層次中傳播 Dispose 模式。

最佳的資源清理策略,應該是同時實現 Finalize 方式和 Dispose 方式。任何重寫了 Finalize 方法的類型都應實現 Dispose 方法。

凡是實現了 Dispose 模式的類型,均可以 using 語句來定義其引用範圍。

 

4.4性能優化的多方探討

—  .NET 性能優化的策略探討

— 多種性能優化分析

4.4.1引言

什麼纔算良好的軟件產品?業務流程、用戶體驗、安全性還有性能,一個都不能少。

4.4.2性能條款

¡  Item1:推薦以 Dispose 模式來代替 Finalize 方式。

¡  Item2:選擇合適的垃圾收集器:工作站 GC 和服務器 GC。

¡  Item3:在適當的情況下對對象實現弱引用。

弱引用是對象引用的一種“中間態”,實現了對象既可以通過 GC 回收其內存,又可被應用程序訪問的機制。對胖對象的內存性能帶來提升。

WeakReference 類用於表示弱引用,通過其 Target 屬性來表示要追蹤的對象,通過其值賦給變量來創建目標對象的強引用。

    MyClass mc = new MyClass();

    //創建弱引用

    WeakReference wr = new WeakReference(mc);

    //移除強引用

    mc = null;

    if (wr.IsAlive)

    {

        //弱引用轉換爲強引用,對象可以再次使用

        mc = wr.Target as MyClass;

    }

    else

    {

        //對象已經被回收,重新創建

        mc = new MyClass();

}

¡  Item4:儘可能以 using 來執行資源清理。

¡  Item5:推薦使用泛型集合來代替非泛型集合。

泛型集合並不能完全代替非泛型集合的應用。

¡  Item6:初始化時最好爲集合對象指定大小。

¡  Item7:特定類型的 Array 性能優於 ArrayList。

¡  Item8:字符串駐留機制,是 CLR 爲 String 類型實現的特殊設計。

¡  Item9:合理使用 System.String 和 System.Text.StringBuilder。

在簡單的字符串操作中使用 String,在複雜的字符串操作中使用 StringBuilder。StringBuilder 在使用上,最好指定合適的容量值。

¡  Item10:儘量在子類中重寫 ToString 方法。

能在一定程度上減少裝箱操作的發生。

¡  Item11:其他推薦的字符串操作。

int StringCompare(string str1, string str2)

以 Length 屬性來判斷字符串判空

¡  Item12:for 和 foreach 的選擇。

推薦選擇 foreach 來處理可枚舉集合的循環結構。

¡  Item13:以多線程處理應對系統設計。

推薦在多線程編程中使用線程池,.NET 提供了 System.Threading.ThreadPool 類來提供對線程池的封裝,一個進程對應一個 ThreadPool,可以被多個 AppDomain 共享。

ThreadPool.QueueUserWorkItem(new WaitCallback(th.MyProcOne), "線程 1");

¡  Item14:儘可能少地拋出異常,禁止將異常處理放在循環內。

儘量用邏輯流程控制來代替異常處理。

¡  Item15:捕獲異常時,catch 塊中儘量指定具體的異常篩選器,多個 catch 塊應該保證異常由特殊到一般的排列順序。

¡  Item16:struct 和 class 的性能比較。

¡  Item17:以 is/as 模式進行類型兼容性檢查。

以 is 來實現類型判斷,以 as 實現安全的類型轉換。

¡  Item18:const 和 static readonly 的權衡。

const 高效,readonly 靈活。

¡  Item19:儘量避免不當的裝箱和拆箱,選擇合適的代替方案。

¡  Item20:儘量使用一維零基數組。

¡  Item21:以 FxCop 工具,檢查你的代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章