.NET框架(轉)

三年前寫的《.NET之美》的第六章,既然.NET平臺這個話題最近比較火,那麼就聊聊吧。沒有將全部章節發佈出來,是因爲想給以後買書的朋友留點實惠。

本書是一本講解.NET技術的書籍,目標讀者羣也是在.NET框架(.NET Framework)下進行開發的程序員,因此我們無法迴避的問題就是:什麼是.NET框架?它包含了哪些內容?爲開發程序提供了哪些支持?很多朋友對這類個問題的第一反應可能是.NET框架所提供的龐大類庫及編寫代碼所採用的C#語言,實際上遠不止這些。

要描述.NET框架,自然會遇到與其相關的一系列專業的技術術語和縮寫,相信大家已經見到過許多了,比如:CLI、CIL、CTS、CLS、CLR、JIT、BCL、FCL、Module、Assembly 等,足以讓很多人一頭霧水、望而卻步。筆者不會像字典一樣按首字母排序對術語進行逐一解釋,因爲這樣還是難以理解。我們還是從大家最熟悉的東西開始吧!

6.1 引子

設想一下:編寫下面這樣一個最簡單的顯示“Hello, World!”的控制檯程序,並將該程序運行起來需要哪幾個步驟呢?

using System;
  
 class Program {
     static void Main(string[] args) {
         string text = "hello, world!";
         Console.WriteLine(text);
     }
 }

這些步驟包括:打開Visual Studio,創建一個C#控制檯應用程序項目(在這裏將它命名爲ConsoleApp),編寫代碼,編譯程序然後運行。雖然這樣的程序誰都會寫,但是再多進行一下思考就會發現,儘管是一個很小的程序,但已經引入了.NET框架的幾個重要方面。

如果創建一個VB.NET類型的項目,實現和上面C#項目完全一樣的功能,那麼編譯後生成的文件有什麼區別?

編寫控制檯應用程序,將字符輸出到屏幕,需要調用Console.WriteLine()方法。這個Console類型從何而來呢?

生成的文件在系統中是如何運行起來的?其機制和使用傳統VC++生成的可執行文件是否相同?

其實,上面每一個問題的答案都包含.NET框架所提供的支持,這裏將它分爲三個部分:

  • 對於編譯後生成的文件格式和內容,.NET中存在着諸多規範。符合這些規範的程序語言,也叫做面向.NET的語言。編譯後生成的文件都可以在.NET運行時下執行,這就是大家所熟知的.NET多語言支持。
  • 在開發階段,.NET提供了一個龐大的類庫,支持開發者快速開發各種應用程序,也支持程序語言設計者開發其語言編譯器。
  • 在程序執行階段,.NET提供了一個程序運行時的環境,這個運行時環境幫助我們管理內存、實時編譯程序、進行安全檢查、執行垃圾回收等。

接下來就針對上述內容開始爲大家詳細講述。

6.2 CIL——公共中間語言

首先要了解的就是C#程序源碼在編譯之後會得到什麼樣的一個文件。大家知道,過去使用VC++生成的可執行文件,經過預編譯、編譯、彙編、鏈接幾個步驟後,最終生成的可執行文件中就已經包含了處理器的本地代碼(Native Code),支持它運行的只是操作系統和本地的機器指令集。那麼採用C#編譯器生成的文件又是什麼呢?現在需要引入程序集這個概念:在.NET框架下,類似C#這樣的高級語言經過編譯後生成的結果文件被稱做程序集,其後綴名是.dll(類庫)或.exe(可執行程序)。在引入這個概念之前,前面(上一節)提到程序集時,都是用“文件”這個詞來描述的。

程序集的定義只是給編譯後生成的文件一個稍微正式一點的名稱,對於解釋“它是由什麼構成的”這個問題並沒有太大的幫助。爲了進一步瞭解程序集,我們再來做一個試驗,使用VB.NET創建一個控制檯應用程序項目(ConsoleAppVB),並生成一個程序集,代碼功能和上面用C#創建的項目是一樣的的。

Module Program
 Sub Main()
 Dim text AsString = "hello, world !"
         Console.WriteLine(text)
 EndSub
 EndModule

現在,需要一個工具來查看這個程序集的內容,並且與C#項目生成的程序集進行對比。還好,微軟已經提供了一個利器——IL DASM(IL Disassembler,IL反彙編程序)來幫助開發者查看程序集的信息。如果安裝了Visual Studio,IL DASM將會隨同Visual Studio一起安裝。依次選擇開始菜單→ Microsoft Visual Studio 2010 → Microsoft Windows SDK Tools →IL 反彙編程序(IL DASM)可以啓動IL DASM。

打開IL DASM後選擇VB.NET項目生成的ConsoleAppVB.exe,可以看到如圖6-1所示的界面。

圖6-1 IL DASM 運行界面

這部分內容很多,會在下一章“程序集”中進行專門講述,,這裏暫且略過。展開圖6-1中的ConsoleAppVB.Program類型,在Main()方法上雙擊,會彈出另外一個窗口,顯示圖6-2中的代碼,看上去有點像彙編語言。在這裏可以看到熟悉的string text變量聲明及“hello, world !”。

圖6-2 方法體的CIL語言描述(VB.NET)

接下來再打開C#項目生成的ConsoleApp.exe,進行同樣的操作,在打開Main()方法後會發現其中的代碼與圖6-2中幾乎完全一樣,如圖6-3所示

圖6-3方法體的CIL語言描述(C#)

至此,可以得到一個初步的推斷:不管是VB.NET還是是C#,編譯之後的程序集都能夠用IL DASM打開,因此它們生成的程序集的格式都是相同的;當程序所實現的功能相同時,程序集所包含的CIL代碼也是類似的。

現在對上面程序集中所包含的類似彙編的語言做一下介紹,即是本節標題中的CIL(Common Intermediate Language,公共中間語言)。CIL最初是隨着.NET由微軟一起發佈的,因此之前也叫做MSIL(Microsoft Intermediate Language),後來進行了標準化,之後便被稱做CIL。在一些書或文章中,CIL也會簡寫爲IL,其實都是指同樣的東西。爲了避免混淆,本書統一用CIL這個縮寫。

我們可以將上面的過程用圖6-4來表示出來。

圖6-4 源程序編譯爲了程序集

接下來再深入地分析一下,公共中間語言這個術語到底包含了哪幾層含義。

  • 公共。因爲不論是C#語言也好,VB.NET語言也好,C++/CLI語言也好,甚至是重新開發的一套以自己的名字縮寫命名的語言,只要它期望運行的目標平臺是.NET,在經過相應的編譯器編譯之後,所生成的程序集就是由CIL語言代碼描述的。
  • 中間。這個詞也是大有深意,爲什麼不叫公共機器語言(Common Machine Language),或者公共本地語言(Common Native Language)?因爲這種語言只是比我們使用的高級語言,比如C#低級一點,並不是CPU可以直接執行的本地機器語言。這種語言還需要.NET運行時(.Net runtime)環境的支持,在執行之前,進行一個被稱爲Just-in-time(即時)的二次編譯過程,才能轉變成計算機可以識別的指令。關於.NET運行時,以及詳細過程後面再介紹,現在只要知道,這個文件所包含的CIL代碼並非機器可以直接執行的指令代碼。
  • 語言。CIL不過是一種程序語言,只不過相對於C#來說,它是一種更低級語言。從圖6-2 的代碼截圖中,已經可以看到,CIL是一種基於堆棧的語言,同時,它提供了class、interface、繼承、多態等諸多面向對象的語言特性,因此它又是完全面向對象的語言。如果願意,甚至可以直接編寫CIL代碼,並且使用CIL的編譯工具IL ASM(IL Assembler,IL彙編程序)來對它進行編譯。只不過,和大多數低級語言一樣,這種方式會使開發效率會變得很低。這裏注意區別一下IL ASM和IL DASM,它們的拼寫是不同的。

爲了加深一下印象,我們來做一個試驗:編寫一段簡單的CIL代碼,並且使用IL ASM工具對其進行編譯,得到和前面一樣的ConsoleApp.exe程序。

1)打開記事本程序,輸入下面的代碼,然後將其保存在D:\ConsoleApp.il。

.assembly extern mscorlib{}
 .assembly ConsoleApp{}
 .module ConsoleApp.exe
 .class public auto ansi Program extends System.Object
 {
     .method public static void Main()
     {
         .entrypoint
         nop
         ldstr "Hello, World!"
         call void [mscorlib]System.Console::WriteLine(string)
         nop
         ret
     }
 }

2)打開Visual Studio 2010命令行工具,輸入:

D:\>ilasm ConsoleApp.il

3)成功後會看到ConsoleApp.exe程序,它的執行結果和上面用C#編寫的完全一樣。

由於程序集是由CIL語言所描述的,因此CIL也叫做程序集語言(Assembly Language)。又因爲.NET程序集需要由.NET運行時加載才能運行,可以視其爲由.NET運行時進行管理的,所以CIL代碼也叫做託管代碼(Managed Code)。相對的,不需要.NET運行時就可以執行的代碼就叫做非託管代碼(Unmanaged Code)。

好了,已經知道了CIL的存在,從現在開始,最好在頭腦裏建立起兩個模型或兩種視角:一種是基於C#或其他高級語言的源程序的視角,一種是基於CIL中間語言的程序集視角。C#源程序在被編譯爲程序集以後,就獨立於C#,因此程序集可以由其他種類的語言所調用;同時,因爲程序集並沒有包含本地機器的指令,所以它與具體的機器類型也分隔開了,可以被裝有.NET框架的任何機器運行。

6.3 BCL和FCL

6.3.1 BCL——基類庫

我們先來看一個有意思的現象:再次打開前面創建的C#控制檯項目(ConsoleApp),然後在解決方案面板下打開“引用”文件夾,如果用的是Visual Studio 2010,並且面向的目標框架是.NET 4.0版本,那麼將會看到如圖6-5所示的這些引用。

圖6-5 解決方案中的“引用”文件夾

在創建項目時並沒有做任何額外的操作,那麼這些引用顯然是在創建項目時自動添加的。爲了方便初學者,這裏稍微解釋一下:要使用(實際上筆者覺得Consume這個詞表達的更貼切)其他開發者所設計的類型,就需要在項目中將該類型所在的程序集引用進來。現在看到的這些程序集引用,都是微軟認爲很常用的,幾乎是每個項目都會使用到的,所以在創建項目時自動添加了進來,免得開發者再手動進行添加。

但是在這裏這些引用不利於我們理解一些內容,所以我們把這些引用全部刪除掉,如圖6-6所示,然後再次編譯程序。

圖6-6 刪除掉所有的項目引用

using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
  
 namespace ConsoleApp {
     classProgram {
         staticvoid Main(string[] args) {
             string text = "Hello, world!";
             Console.WriteLine(text);
         }
     }
 }

可能有人會認爲,在刪掉這些引用之後,編譯器將會毫不客氣地提示編譯錯誤:未能找到類型或命名空間“System”(是否缺少using指令或程序集引用?)。可實際上,當編譯並運行上面的代碼時,程序會正確無誤地執行。這是因爲我們已經刪掉了所有引用的程序集,只定義了一個Program類型,並沒有定義Console類型,所以此時要面對的第一個問題就是:Console類型從哪裏來?

Visual Studio提供了一個快捷的辦法使我們可以快速查看類型:將光標定位在Console上,然後按下鍵盤上的F12,就可以看到Console的類型定義。在Console類型定義的最上方,可以看到它所在的程序集地址:C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll。

#region 程序集 mscorlib.dll, v4.0.30319
 // C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll
 #endregion
 using System.IO;
 using System.Runtime.ConstrainedExecution;
 using System.Security;
 using System.Text;
  
 namespace System {
     public static class Console {
         // 中間略
    }
 }

可以看到Console類型來自於mscorlib.dll這個程序集。從上面的實驗可以看出,不管我們是否引用mscorlib.dll程序集,它總是會自動引用進來。這個程序集中所包含的類庫,即是本節標題中的BCL(Base Class Library,基類庫)。從名字就可以看出來,這個類庫包含的都是些最基本的類型,其本身已經與CIL語言融爲一提了,爲CIL語言提供基礎的編程支持,以至於該類庫已經成爲了CLI標準的一部分(後面會介紹,因此也可以說BCL中的類型就是CIL語言的類型,所有面向CIL的語言都能夠使用它們。我們可以使用對象瀏覽器(Visual Studio菜單→視圖→對象瀏覽器)來查看mscorlib.dll程序集中都包含了哪些命名空間和類型,如圖6-7所示。

圖6-7 mscorlib.dll中包含的命名空間

可以看到該程序集下包含的主要是System命名空間,稍微細心一點的讀者會發現,在新建項目的時候,還包含了System.dll程序集,並且其中所包含的類型與mscorlib中的類型十分相似。

圖6-8 System 程序集

圖6-9 System.dll中包含的命名空間

這又是怎麼回事呢?實際上,只要點開System命名空間就會發現,mscorlib.dll的System命名空間下面定義的類型和System.dll的System命名空間下面定義的類型完全不同,它們之間並沒有衝突之處。

現在就明白了:BCL提供了像Console這樣的類型來支持開發者編寫類似控制檯這樣的程序。

既然已經思考了這麼多,不妨再深入一下,思考這樣一個問題:寫下的這條語句string text = “hello, world !”,其中的string從哪裏來?從直覺來看,string在Visual Studio中以深藍色呈現,屬於C#的關鍵字,那麼它應該是C#提供的內置類型。可是,當我們將光標移動到string上並按下F12時,轉到string的定義時,看到的卻是下面這樣的內容:

#region 程序集 mscorlib.dll, v4.0.30319
 // C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\mscorlib.dll
 #endregion
  
 using System.Collections;
 using System.Collections.Generic;
 // 爲了節約篇幅,省略了一些using
  
 namespace System {
     public sealed class String : IComparable, ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, IEnumerable, IEquatable<string> {
     // 省略定義
    }
 }

注意最上方的程序集地址,再次看到了mscorlib.dll,並且String類型與Console類型一樣,同位於System命名空間下。由此可見,C#的關鍵字string,不過是BCL中System.String類型的一個別名而已。類似地,VB.NET中的String關鍵字也是BCL中的System.String類型的別名。因此,在.NET框架中,語言從本質上來說沒有太大的區別,更多的區別是在語法方面。從上面的例子也可以看出,C#和VB.NET的很多語言能力並不是自己的,而是從CIL“借”過來的這樣做也保證了在不同語言中相應類型的行爲是一致的。

表6-1列出了幾個典型的,不同語言關鍵字與CIL類型的對應關係。筆者覺得理解重於記憶,所以這裏只列出了幾個。要了解其他基礎類型時,只要將光標移動到類型上,然後再按下F12鍵就可以了。

表6-1不同語言關鍵字與CIL類型的對應關係

CIL 類型 C# 關鍵字 VB.NET關鍵字
System.Byte byte Byte
Sytem.Int16 short Short
System.Int64 int Integer

從表6-1可以看出,.NET同時也對語言開發者提供支持.如你需要設計一款語言,那麼在開發編譯器時將語言的關鍵字映射爲CIL中的類型就可以了,也就是說,對自己語言中的一些特殊符號(關鍵字)進行映射處理,就好像C#中的關鍵字int和string一樣。

大家可能聽說過這樣一種特殊的類型——基元類型(Primitive Type)。實際上,講到這裏大家應該已經明白了,那些由編譯器直接支持,將語言本身的關鍵字類型轉換爲CIL類型的,就叫做基元類型。顯然,上面的byte、int、string都是基元類型。而C#中並沒有一個關鍵字去映射Console,所以我們認爲Console只是普通的類類型(Class Type)。

6.3.2 FCL——框架類庫

作爲一名.NET程序員,每天都要打交道的就是FCL了(Framework Class Library,框架類庫)。在上一節中介紹了BCL,它是FCL的一個子集。BCL中包含了與編譯器及CIL語言關係緊密的核心類型,以及常見開發任務中都會使用到的類型。而FCL包含的內容極多,僅服務於一種應用場景的子類庫就足夠寫一本書了,這裏僅簡單對它進行介紹。

從功能上來看,可以將FCL框架類庫劃分成以下幾層。

  • 最內一層,由BCL的大部分組成,主要作用是對.NET框架、.NET運行時及CIL語言本身進行支持,例如基元類型、集合類型、線程處理、應用程序域、運行時、安全性、互操作等。
  • 中間一層,包含了對操作系統功能的封裝,例如文件系統、網絡連接、圖形圖像、XML操作等。
  • 最外一層,包含各種類型的應用程序,例如Windows Forms、Asp.NET、WPF、WCF、WF等。

6.4 CTS——公共類型系統

假設要開發一套新的語言,這種語言和C#或VB.NET一樣,在編譯後也能夠生成CIL代碼,也可以在.NET環境下運行,那麼首先需要什麼呢?

根據6.2節所講述的內容我們知道,要開發的新語言相當於CIL的高級語言版本,所以實際上要做什麼並不是由新語言決定的,而是由CIL來決定的。因此,需要一套CIL的定義、規則或標準。這套規則定義了我們的語言可以做什麼,不可以做什麼,具有哪些特性。這套規則就稱作CTS(Common Type System,公共類型系統)。任何滿足了這套規則的高級語言就可以稱爲面向.NET框架的語言。C#和VB.NET不過是微軟自己開發的一套符合了CTS的語言,實際上還有很多的組織或團體,也開發出了這樣的語言,比如Delphi.Net、FORTRAN等。

那麼CTS具體包括哪些內容呢?在回答這個問題之前我們需要弄清楚一個概念。還是通過一段C#代碼來說明,先看下面幾行代碼:

public class Book { 
 // 省略實現
}
Book item1 = new Book();
 Book item2 = new Book(); 

對於以上代碼,通常是這麼描述的:定義了一個Book類,並且創建了兩個Book類的實例item1、item2。實際上這隻包含了兩層含義如表6-2所示。

表6-2 類、類的實例

Book
類的實例 item1,item2

再思考一下就會發現,還有一個更高的層面,那就是Book這個類的類型,我們稱之爲類類型(Class Type),因此上表可以改成如表6-3所示。

表6-3 類類型、類、類的實例

類類型 class
Book
類的實例 item1,item2

類似的,還有枚舉類型(Enum Type)、結構類型((Struct Type)等。現在大家應該明白這裏要表達的意思了,CTS規定了可以在語言中定義諸如類、結構、委託等類型,這些規則定義了語言中更高層次的內容。因此,在C#這個具體的語言實現中,我們纔可以去定義類類型(Class Type)或者結構類型(Struct Type)等。

同樣,可以在Book類中定義一個字段name並提供一個方法ShowName()。實際上,這些也是CTS定義的,它規範了類型中可以包含字段(filed)、屬性(property)、方法(method)、事件(event)等。

除了定義各種類型外,CTS還規定了各種訪問性,比如Private、Public、Family(C#中爲Protected)、Assembly(C#中爲internal)、Family and assembly(C#中沒有提供實現)、Family or assembly(C#中爲protected internal)。

CTS還定義了一些約束,例如,所有類型都隱式地繼承自System.Object類型,所有類型都只能繼承自一個基類。從CTS的名稱和公共類型系統可以看出,不僅C#語言要滿足這些約束,所有面向.NET的語言都需要滿足這些約束。衆所周知,傳統C++是可以繼承自多個基類的。爲了讓熟悉C++語言的開發者也能在.NET框架上開發應用程序,微軟推出了面向.NET的C++/CLI語言(也叫託管C++),它就是符合CTS的C++改版語言,爲了滿足CTS規範,它被限制爲了只能繼承自一個基類。

關於上面內容有兩點需要特別說明:

1)C#並沒有提供Family and assembly的實現,C#中也沒有全局方法(Global Method)。換言之,C#只實現了CTS 的一部分功能。,也就是說,CTS規範了語言能夠實現的所有能力,但是符合CTS規範的具體語言實現不一定要實現CTS規範所定義的全部功能。

2)C++/CLI又被約束爲只能繼承自一個基類,換言之,C++中的部分功能被刪除了。,就是說,任何語言要符合CTS,其中與CTS不兼容的部分功能都要被捨棄。

顯然,由於CIL是.NET運行時所能理解的語言,因此它實現了CTS的全部功能。雖然它是一種低級語言,但是實際上,它所具有的功能更加完整。C#語言和CIL的關係,可以用圖6-10進行表示。

圖6-10 C#和CIL的關係

6.5 CLS——公共語言規範

既然已經理解了CTS是一套語言的規則定義,就可以開發一套語言來符合CTS了。假設這個語言叫做N#,它所實現的CTS非常有限,僅實現了其中很少的一部分功能,它與CTS和C#語言的關係可能如圖6-11所示。

圖6-11 C#、N#和CIL的關係

那麼現在就有一個問題:由C#編寫的程序集,能夠引用由N#編寫的程序集嗎?答案顯然是不能,,雖然C#和N#同屬於CTS旗下,但是它們並沒有共通之處。因此,雖然單獨的N#或C#程序可以完美地在.NET框架下運行,但是它們之間卻無法相互引用。如果使用N#開發項目的開發者本來就不希望其他語言類型的項目來引用他的項目倒也罷了,但是,如果N#項目期望其他語言類型的項目能夠對它進行引用,就需要N#中公開的類型和功能滿足C#語言的特性,即它們需要有共通之處。注意,這句話中有一個詞很重要,就是“公開的”(public)。N#中不公開的部分(private、internal、protected)是不受影響的,可以使用獨有的語言特性,因爲這些不公開的部分本來就不允許外部進行訪問。因此, 如果N#想要被C#所理解和引用,它公開的部分就要滿足C#的一些規範,此時,它與CTS和C#語言的關係就會變成如圖6-12所示。

圖6-12 C#、N#、CIL的關係

如果世界上僅有C#和N#兩種語言就好辦了,把它們共同的語言特性提取出來,然後要求所有公開的類型都滿足這些語言特性,這樣C#和N#程序集就可以相互引用了。可問題是:語言類型有上百種之多,並且.NET的設計目標是實現一個開放的平臺,不僅現有的語言經過簡單修改就可以運行在.NET框架上,後續開發的新語言也可以,而新語言此時並不存在,如何提取出它的語言特性?因此又需要一套規範和標準來定義一些常見的、大多數語言都共有的語言特性。對於未來的新語言,只要它公開的部分能夠滿足這些規範,就能夠被其他語言的程序集所使用。這個規範就叫做CLS (Common Language Specification,公共語言規範)。很明顯,CLS是CTS的一個子集。現在引入了CLS,圖6-12的關係圖就可以改成如圖6-13所示。

圖6-13 語言、CLS、CIL的關係

如果利用C#開發的一個程序集的公開部分僅採用了CLS中的特性,那麼這個程序集就叫做CLS兼容程序集(CLScompliant assembly)。顯然,對於上面提到的FCL框架類庫,其中的類型都符合CLS,僅有極個別類型的成員不符合CLS,這就保證了所有面向.NET的語言都可以使用框架類庫中的類型。

現在,讀者又會有一個疑問:上面幾段文字中反覆出現了一個詞———“語言特性”(language features),滿足CLS就是要求語言特性要一致,那麼什麼叫做語言特性?這裏給出幾個具體的語言特性:是否區分大小寫,標識符的命名規則如何,可以使用的基本類型有哪些,構造函數的調用方式(是否會調用基類構造函數),支持的訪問修飾符等。

那麼我們如何檢驗程序集是否符合CLS呢?.NET爲我們提供了一個特性CLSCompliant,便於在編譯時檢查程序集是否符合CLS。我們來看下面一個例子:

using System;
  
 [assembly:CLSCompliant(true)]
  
 public class CLSTest {
  
     public string name;
  
     // 警告:僅大小寫不同的標識符“CLSTest.Name()”不符合 CLS
     public string Name() {
         return "";
     }
  
     // 警告:“CLSTest.GetValue()”的返回類型不符合 CLS
     public uint GetValue() {
         return 0;
     }
  
     // 警告: 參數類型“sbyte”不符合 CLS
     public void SetValue(sbyte a) { }
  
     // 警告標識符“CLSTest._aFiled”不符合 CLS
     public string _MyProperty { get; set; }
 }

可以注意到,在CLSTest類的前面爲程序集加上了一個CLSCompliant特性,表明這個程序集是CLS兼容的。但是,有三處並不滿足這個要求,因此編譯器給出了警告信息。這三處是:

  • 不能以大小寫來區分成員,因此字段name和方法Name()不符合CLS。
  • 方法的返回類型和參數類型必須是CLS兼容的,uint和sbyte類型並非CLS兼容,因此GetValue()和SetValue()方法不符合CLS。
  • 標識符的命名不能以下劃線“_”開頭,因此屬性_MyProperty不符合CLS。

還會注意到,編譯器給出的只是警告信息,而非錯誤信息,因此可以無視編譯器的警告,不過這個程序集只能由其他C#語言編寫的程序集所使用。

6.6 CLR——公共語言運行時

6.6.1 程序集概述

前面提到過:程序集包含了CIL語言代碼,而CIL語言代碼是無法直接運行的,需要經過.NET運行時進行即時編譯才能轉換爲計算機可以直接執行的機器指令。那麼這個過程是如何進行的呢?

接下來我們要了解的就是.NET框架的核心部分:CLR(Common Language Runtime),公共語言運行時),有時也會稱做.NET運行時(.NET runtime)。在瞭解CLR之前,需要先進一步學習一下程序集,因爲下一節會對程序集進行專門的講述,這裏僅簡單介紹一下程序集中對於理解CLR有幫助的概念。

從直覺上來看,前面以.exe爲後綴的控制檯應用程序就是一個直接的可執行文件,因爲在雙擊它後,它確實會運行起來。這裏的情況和麪向對象中的繼承有一點像:一臺轎車首先是一部機動車、一隻貓首先是一個動物,而一個.NET程序集首先是一個Windows可執行程序。

那麼什麼樣格式的文件纔是一個Windows可執行文件?這個格式被稱做PE/COFF(Microsoft Windows Portable Executable/Common Object File Format),Windows可移植可執行/通用對象文件格式。Windows操作系統能夠加載並運行.dll和.exe是因爲它能夠理解PE/COFF文件的格式。顯然,所有在Windows操作系統上運行的程序都需要符合這個格式,當然也包括.NET程序集在內。在這一級,程序的控制權還屬於操作系統,PE/COFF頭包含了供操作系統查看和利用的信息。此時,程序集可以表示成如圖6-14所示。

圖6-14 程序集結構1

在前面提到過,程序集中包含的CIL語言代碼並不是計算機可以直接執行的,還需要進行即時編譯,那麼在對CIL語言代碼進行編譯前,需要先將編譯的環境運行起來,因此PE/COFF頭之後的就是CLR頭了。CLR頭最重要的作用之一就是告訴操作系統這個PE/COFF文件是一個.NET程序集,區別於其他類型的可執行程序。

圖6-15 程序集結構2

在CLR頭之後就是大家相對熟悉一些的內容了。首先,程序集包含一個清單(manifest),這個清單相當於一個目錄,描述了程序集本身的信息,例如程序集標識(名稱、版本、文化)、程序集包含的資源(Resources)、組成程序集的文件等。

圖6-16 程序集結構3

清單之後就是元數據了。如果說清單描述了程序集自身的信息,那麼元數據則描述了程序集所包含的內容。這些內容包括:程序集包含的模塊(會在第7章介紹)、類型、類型的成員、類型和類型成員的可見性等。注意,元數據並不包含類型的實現,有點類似於C++中的.h頭文件。在.NET中,查看元數據的過程就叫做反射(Reflection)。

圖6-17 程序集結構4

接下來就是已經轉換爲CIL的程序代碼了,也就是元數據中類型的實現,包括方法體、字段等,類似於C++中的.cpp文件。

圖6-18 程序集結構

注意,圖6-18中還多添加了一個資源文件,例如.jpg圖片。從這幅圖可以看出,程序集是自解釋型的(Self-Description),不再需要任何額外的東西,例如註冊表,就可以完整地知道程序集的一切信息。

至此對程序集的簡單介紹就先到這裏,接下來看一下程序集是如何被執行的。

6.6.2 運行程序集

現在已經瞭解過了程序集,並且知道程序集中包含的CIL代碼並不能直接運行,還需要CLR的支持。概括來說,CLR是一個軟件層或代理,它管理了.NET程序集的執行,主要包括:管理應用程序域、加載和運行程序集、安全檢查、將CIL代碼即時編譯爲機器代碼、異常處理、對象析構和垃圾回收等。相對於編譯時(Compile time),這些過程發生在程序運行的過程中,因此,將這個軟件層命名爲了運行時,實際上它本身與時間是沒有太大關係的。有一些朋友在初學.NET的時候,糾結在了Runtime這個詞上,總以爲和時間有什麼關係,總是不能很好地理解CLR。筆者認爲重要的是理解CLR是做什麼的,而不用過於關注它的名稱。

實際上,CLR還有一種叫法,即VES(Virtual Execution System,虛擬執行系統)。從上一段的說明來看,這個命名應該更能描述CLR的作用,也不容易引起混淆,但是可能爲了和CIL、CTS、CLS等術語保持一致性,最後將其命名爲了CLR。在這裏,我們知道CLR不過是一個.NET程序集的運行環境而已,有點類似於Java虛擬機。VES這個術語來自於CLI,會在6.7節進行講述。

可以用圖6-19來描述CLR的主要作用。

圖6-19 CLR的主要作用

前面已經概要地瞭解了CLR的作用,接下來開始更進一步的學習。首先遇到的問題就是:CLR以什麼樣的形式位於什麼位置?

由於CLR本身用於管理託管代碼,因此它是由非託管代碼編寫的,並不是一個包含了託管代碼的程序集,也不能使用IL DASM進行查看。它位於C:\%SystemRoot%\Microsoft.NET\Framework\版本號下,視安裝的機器不同有兩個版本,一個是工作站版本的mscorwks.dll,一個是服務器版本的mscorsvr.dll。wks和svr分別代表work station和server。

接下來再看一下CLR是如何運行起來的。雖然從Windows Server 2003開始,.NET框架已經預裝在操作系統中,但是它還沒有集成爲操作系統的一部分。當操作系統嘗試打開一個託管程序集(.exe)時,它首先會檢查PE頭,根據PE頭來創建合適的進程。

接下來會進一步檢查是否存在CLR頭,如果存在,就會立即載入MsCorEE.dll。這個庫文件是.NET框架的核心組件之一,注意它也不是一個程序集。MsCorEE.dll位於C:\%SystemRoot%\System32\系統文件夾下所有安裝了.NET框架的計算機都會有這個文件。大家可能注意到了,這個庫安裝在System32系統文件夾下,而沒有像其他的核心組件或類庫那樣按照版本號存放在C:\%SystemRoot%\Microsoft.NET\Framework\文件夾下。這裏又存在一個“雞生蛋問題”:根據不同的程序集信息會加載不同版本的CLR,因此加載CLR的組件就應該只有一個,不能再根據CLR的版本去決定加載CLR的組件的版本。

MsCorEE.dll是一個很細的軟件層。加載了MsCorEE.dll之後,會調用其中的_CorExeMain()函數,該函數會加載合適版本的CLR。在CLR運行之後,程序的執行權就交給了CLR。CLR會找到程序的入口點,通常是Main()方法,然後執行它。這裏又包含了以下過程:

  1. 加載類型。在執行Main()方法之前,首先要找到擁有Main()方法的類型並且加載這個類型。CLR中一個名爲Class loader(類加載程序)的組件負責這項工作。它會從GAC、配置文件、程序集元數據中尋找這個類型,然後將它的類型信息加載到內存中的數據結構中。在Class loader找到並加載完這個類型之後,它的類型信息會被緩存起來,這樣就無需再次進行相同的過程。在加載這個類以後,還會爲它的每個方法插入一個存根(stub)。
  2. 驗證。在CLR中,還存在一個驗證程序(verifier),該驗證程序的工作是在運行時確保代碼是類型安全的。它主要校驗兩個方面,一個是元數據是正確的,一個是CIL代碼必須是類型安全的,類型的簽名必須正確。
  3. 即時編譯。這一步就是將託管的CIL代碼編譯爲可以執行的機器代碼的過程,由CLR的即時編譯器(JIT Complier)完成。即時編譯只有在方法的第一次調用時發生。回想一下,類型加載程序會爲每個方法插入一個存根。在調用方法時,CLR會檢查方法的存根,如果存根爲空,則執行JIT編譯過程,並將該方法被編譯後的本地機器代碼地址寫入到方法存根中。當第二次對同一方法進行調用時,會再次檢查這個存根,如果發現其保存了本地機器代碼的地址,則直接跳轉到本地機器代碼進行執行,無需再次進行JIT編譯。

可以看出,採用這種架構的一個好處就是,.NET程序集可以運行在任何平臺上,不管是Windows、UNIX,還是其他操作系統,只要這個平臺擁有針對於該操作系統的.NET框架就可以運行.NET程序集。

6.7 CLI——公共語言基礎

CLI是一個國際標準,由ECMA和ISO進行了標準化,全稱爲Common Language Infrastructure(公共語言基礎)。它只是一個概念和彙總,實際上本章的每一小節都是這個標準的一部分。CLI包括:CIL、CTS、CLS、VES、元數據、基礎框架。

看到這裏很多人會感覺到有點奇怪,爲什麼CLI和.NET框架包含的內容如此雷同?它們之間是什麼關係?簡單來說,CLI是一個標準,而.NET框架是這個標準的具體實現。在CLI中,並沒有CLR的概念,只有VES,而CLR就是.NET框架中VES的具體實現。既然CLI只是一個標準,而.NET框架是它在Windows平臺上的具體實現,那麼是不是就只有.NET框架這一個CLI的實現?顯然不是,Mono Project就是CLI標準的另一個實現。Mono Project的目標就是將.NET框架多平臺化,使其可以運行在各種平臺上,包括Mac OS、Linux等。

CLI的詳細信息可以在這裏查看:http://www.ecma-international.org/publications/standards/Ecma-335.htm,感興趣的朋友可以將它的PDF標準文檔下載下來看一下。

6.8 本章小結

本章系統的學習地介紹了一下.NET框架的底層知識,幾乎包含了常見的所有術語,例如程序集、CIL、CTS、CLS、CLR等,同時也介紹了它們之間是如何相互協作共同構建起整個.NET平臺的。相信經過本章的學習,大家會對.NET框架有一個更好的全局性認識。

感謝閱讀,希望這篇文章能給你帶來幫助。

轉自:http://www.cnblogs.com/JimmyZhang/archive/2012/11/27/2790759.html

-------等待《.net之美》出版

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