在第1章中,咱們在高層次上觀察了編譯過程。編譯器接受源代碼文件並生稱名稱爲程序集的輸出文件。這一章中,咱們將詳細闡述程序集以及它們是如何生成和部署的。你還會看到命名空間是如何幫助組織類型的。
在迄今爲止所看到的全部程序中,大部分都聲明並使用它們本身的類。然而,在許多項目中,你會想使用來自其餘程序集的類或類型。這些其餘的程序集可能來自BCL,或來自第三方供應商,或你本身建立了它們。這些程序集稱爲類庫,並且它們的程序集文件的名稱一般以.dll擴展名結尾而不是.exe擴展名。
例如,假設你想建立一個類庫,它包含能夠被其餘程序集使用的類和類型。一個簡單庫的源代碼以下例所示,它包含在名稱爲SuperLib.cs文件中。該庫含有一個名稱爲SquareWidget的公有類。下圖闡明瞭DLL的生成。程序員
public class SquareWidget { public double SideLength=0; public double Area { get{return SideLength*SideLength;} } }
要使用Visual Studio建立類庫,在已安裝的Windows模板中建立類庫模板。具體來講,在Visual Studio中進行的操做步驟以下。緩存
假設你還要寫一個名稱爲MyWidgets的程序,並且你想使用SquareWidget類。程序的代碼在一個名稱爲MyWidgets.cs的文件中,以下例所示。這段代碼簡單建立一個類型爲SquareWidget的對象並使用該對象的成員。安全
using System; class WidgetsProgram { static void Main() { SquareWidget sq = new SquareWidget(); //來自類庫 ↑ 未在當前程序集中聲明 sq.SideLength = 5.0; //設置邊長 Console.WriteLine(sq. Area); //輸出該區域 } ↑ } 未在當前程序集中聲明
注意,這段代碼沒有聲明類SquareWidget。相反,使用的是定義在SuperLib中的類。然而,當你編譯MyWidgets程序時,編譯器必須知道你的代碼在使用程序集SuperLib,這樣它才能獲得關於類SquareWidget的信息。要實現這點,須要給編譯器一個到該程序集的引用,給出它的名稱和位置。
在Visual Studio中,能夠用下面的方法把引用添加到項目。框架
在添加了引用以後,能夠編譯MyWidgets了。下圖闡明瞭所有的編譯過程。
ide
有一個類庫,我幾乎在先前的每個示例中都使用它。它就是包含Console類的那個庫。Console類被定義在名稱爲mscorlib的程序集中,在名稱爲mscorlib.dll的文件裏。然而,你不會看到這個程序集被列在References目錄中。程序集mscorlib.dll含有C#類型以及大部分.NET語言的基本類型的定義。在編譯C#程序時,它必須老是被引用,因此Visual Studio不把它顯示在References目錄中。
若是算上mscorlib,MyWidgets的編譯過程看起來更像下圖所示的表述。在此以後,我會假定使用mscorlib程序集而再也不描述它。
如今假設你的程序已經很好地用SquareWidget類工做了,但你想擴展它的能力以使用一個名稱爲CircleWidget的類,它被定義在另外一個名稱爲UltraLib的程序集中。MyWidgets的源代碼看上去像下面這樣。它建立一個SquareWidget對象和一個CircleWidget對象,它們分別定義在SuperLib中和UltraLib中。工具
class WidgetsProgram { static void Main() { SquareWidget sq = new SquareWidget (); //來自 SuperLib … CircleWidget circle = new CircleWidget(); //來自 UltraLib … } }
類庫UltralLib的源代碼以下面的示例所示。注意,除了類CircleWidget以外,就像庫SuperLib, 它還聲明瞭一個名稱爲SquareWidget的類 。能夠把UltraLib 編譯成一個DLL並加入到項目MyWidgets的引用列表中。測試
public class SquareWidget { … } public class CircleWidget { public double Radius =0; public double Area { get {…} } }
由於兩個庫都含有名稱爲SquareWidget的類,當你試圖編譯程序MyWidgets時,編譯器產生一條錯誤消息,由於它不知道使用類SquareWidget的哪一個版本。下圖闡明瞭這種命名衝突。
ui
在MyWidgets示例中,因爲你有源代碼,你能經過在SuperLib源代碼或UltraLib源代碼中僅僅改變SquareWidget類的名稱來解決命名衝突。可是,若是這些類是由不一樣的公司開發的,並且你還不能擁有源代碼會怎麼樣呢?假設SuperLib由一個名稱爲MyCorp的公司生產,UltraLib由ABCCorp公司生產。在這種狀況下,若是你使用了任何有衝突的類或類型,你將不能把這兩個庫放在ー起使用。
你能想象得出,在你作開發的機器上含有許多不一樣公司生產的程序集,極可能有必定數量的類名重複。若是僅僅由於它們碰巧有共同的類型名,不能把兩個程序集用在一個程序中,這將很惋惜。
可是,假設MyCorp有一個策略,讓全部類的前綴都是公司名字加上類產品名和描述名。而且進一步假設ABCCorp也有相同的策略。這樣的話,咱們示例中的3個類名就多是MyCorpSuperLibSquareWidget、ABCCorpUltraLibSquareWidget和ABCCorpUltralLibCircleWidget,以下圖所示。這固然是徹底有效的類名,而且一個公司類庫的類不太可能與其餘公司類庫的類發生衝突。
可是,在咱們的示例程序中,須要使用冗長的名字,看上去以下所示:spa
class WidgetsProgram { static void Main() { MyCorpSuperLibSquareWidget sq =new MyCorpSuperLibSquareWidget(); //來自SuperLib … ABCCorpUltraLibCircleWidget circle =new ABCCorpUltraLibCircleWidget(); //來自UltraLib … } }
儘管這能夠解決衝突問題,可是即便有智能感知,這些新的、已消除歧義的名字仍是難以閱讀而且容易出錯。
不過,假設除了標識符中通常容許的字符,還能夠在字符串中使用點--儘管不是在類名的最前面或最後面,那麼這些名字就更好理解了,好比MyCorp.SuperLib.SquareWidget、ABCCorp.UltraLib.SquareWidget及ABCCorp.UltraLib.CircleWidget。如今代碼看上去以下所示:操作系統
class WidgetsProgram { static void Main() { MyCorp.SuperLib.SquareWidget sq =new MyCorp.SuperLib.SquareWidget(); //來自SuperLib … ABCCorp.UltraLib.CircleWidget circle =new ABCCorp.UltraLib.CircleWidget(); //來自UltraLib … } }
這就給了咱們命名空間名和命名空間的定義。
你可使用命名空間來把一組類型組織在一塊兒而且給它們起一個名字。通常而言,命名空間名描述的是命名空間中包含的類型,而且和其餘命名空間名不一樣。
你能夠經過在包含你的類型聲明的源文件中聲明命名空間,從而建立命名空間。以下代碼演示了聲明命名空間的語法。而後在命名空間聲明的大括號中聲明你的全部類和其餘類型。那麼這些類型就是這個命名空間的成員了。
關鍵字 命名空間名 ↓ ↓ namespace NamespaceName { TypeDeclarations }
以下代碼演示了MyCorp的程序員如何建立MyCorp.SuperLib命名空間以及聲明其中的Squarewidget類。
公司名 點 ↓ ↓ namespace MyCorp.SuperLib { public class SquareWidget { public double SideLength = 0; public double Area { get { return SideLength * SideLength; } } } }
當MyCorp公司給你配上更新的程序集時,你能夠經過按照以下方式修改MyWidgets程序來使用它。
class WidgetsProgram { static void Main( ) { 徹底限定名 徹底限定名 ↓ ↓ MyCorp.SuperLib.SquareWidget sq = new MyCorp.SuperLib.SquareWidget(); ↑ ↑ 命名空間名 類名 CircleWidget circle = new CircleWidget(); … } }
既然你在代碼中顯式指定了SquareWidget的SuperLib版本,編譯器不會再有區分類的問題了。徹底限定名稱輸入起來有點長,但至少你如今能使用兩個庫了。在本章稍後,我會闡述using別名指令以解決不得不在徹底限定名稱中重複輸入的麻煩。
若是UltraLib程序集也被生產它的公司(ABCCorp)使用命名空間更新,那麼編譯過程會以下圖所示。
如你所見,命名空間的名稱能夠包含建立該程序集的公司的名稱。除了標識公司之外,該名稱還用於幫助程序員快速瞭解定義在命名空間內的類型的種類。
關於命名空間名稱的一些要點以下。
下表列出了一些在.NET BCL中的命名空間的名稱。
下面是命名空間命名指南:
例如,Acme Widget公司的軟件開發部門在下面3個命名空間中開發軟件,以下面的代碼所示:
namespace AcmeWidgets.SuperWidget { class SPDBase … … }
關於命名空間,有其餘幾個要點應該知道。
下圖左邊展現了一個源文件,它順序聲明瞭兩個命名空間,每一個命名空間內有幾個類型。注意,儘管命名空間內含有幾個共有的類名,它們被命名空間名稱區分開來,如右邊的程序集所示。
.NET框架BCL提供了數千個已定義的類和類型以供生成程序時選擇。爲了幫助組織這組有用的功能,相關功能的類型被聲明在相同的命名空間裏。BCL使用超過100個命名空間來組織它的類型。
命名空間不是封閉的。這意味着能夠在該源文件的後面或另外一個源文件中再次聲明它,以對它增長更多的類型聲明。
下面展現了三個類的聲明,它們都在相同的命名空間中,但聲明在分離的源文件中。源文件能夠被編譯成單一的程序集(圖21-9),或編譯成分離的程序集(圖21-10)。
命名空間能夠被嵌套,從而產生嵌套的命名空間。嵌套命名空間容許你建立類型的概念層次。有兩種方法聲明一個嵌套的命名空間,以下所示。
上圖所示的兩種形式的嵌套命名控件聲明生成相同的程序集。下圖展現了兩個聲明在SomeLib.cs文件中的類,使用它們的徹底限定名。
雖然嵌套命名空間位於父命名空間內部,可是其成員並不屬於包裹的父命名空間。有一個常見的誤區,認爲既然嵌套的命名空間位於父命名空間內部,其成員也是父命名空間的子集,這是不正確的,命名空間之間是相互獨立的。
徹底限定名可能至關長,在代碼中通篇使用它們十分繁瑣。然而,有兩個編譯器指令,可使你避免使用徹底限定名:using命名空間指令和using別名指令。
關於using指令的兩個要點以下:
在MyWidgets示例中,你看到多個部分使用徹底限定名稱指定一個類。能夠經過在源文件的頂端放置using命名空間指令以免不得不使用長名稱。
using命名空間指令通知編譯器你將要使用來自某個指定命名空間的類型。而後你可使用簡單類名而沒必要全路徑修飾它們。
當編譯器遇到一個不在當前命名空間的名稱時,它檢査在using命名空間指令中給出的命名空間列表,並把該未知名稱加到列表中的第一個命名空間後面。若是結果徹底限定名稱匹配了這個程序集或引用程序集中的一個類,編譯器將使用那個類。若是不匹配,那麼它試驗列表中下一個命名空間。
using命名空間指令由關鍵字using跟着一個命名空間標識符組成。
關鍵字 ↓ using System; ↑ 命名空間的名稱
WriteLine方法,就是類Console的成員,在System命名空間中。不是在通篇代碼中使用它的徹底限定名,我只是簡化了一點咱們的工做,在代碼的頂端使用using命名空間指令。
例如,下面的代碼在第一行使用using命名空間指令以描述該代碼使用來自System命名空間的類或其餘類型。
using System; … System.Console.WriteLine("This is text 1");//使用徹底限定名 COnsole.WriteLine("This is text 2); //使用指令
using別名指令容許起一個別名給:
例如,下面的代碼展現了兩個using別名指令的使用。第一個指令告訴編譯器標識符Syst是命名空間System的別名。第二個指令代表標識符SC是類System.Console的別名。
關鍵字 別名 命名空間 ↓ ↓ ↓ using Syst=System; using SC=System.Console; ↑ 類
下面的代碼使用這些別名。在Main中3行代碼都調用System.Console.WriteLine方法。
using Syst = System; // using 命名空間別名指令 using SC = System.Console; // using 類別名指令 namespace MyNamespace { class SomeClass { static void Main() { Syst.Console.WriteLine("Using the namespace alias."); System.Console.WriteLine("Using fully qualified name."); SC.WriteLine("Using the type alias"); }類的別名 } } }
如第1章所述,程序集不包含本地機器代碼,而是公共中間語言代碼。它還包含實時編譯器(JIT)在運行時轉換CIL到本機代碼所需的一切,包括對它所引用的其餘程序集的引用。
程序集的文件擴展名一般爲.exe或.dll。
大部分程序集由一個單獨的文件構成。下圖闡明瞭程序集的4個主要部分。
程序集代碼文件稱爲模塊。儘管大部分程序集由單文件組成,但有些也有多個文件。對於有多個模塊的程序集,一個文件是主模塊(primary module ),而其餘的是次要模塊(secondary modules )。
下圖闡明瞭一個帶次要模塊的多文件程序集。
在.NET框架中,程序集的文件名不像在其餘操做系統和環境中那麼重要,更重要的是程序集的標識符(identity )。
程序集的標識符有4個組成部分,它們一塊兒惟一標識了該程序集,以下所示。
公鑰是公鑰/私鑰對的一部分,它們是一組兩個很是大的、特別選擇的數字,能夠用於建立安全的數字簽名。公鑰,顧名思義,能夠被公開。私鑰必須被擁有者保護起來。公鑰是程序集標識符的一部分。咱們稍後會在本章看到私鑰的使用。
程序集名稱的組成被包含在程序集清單中。下圖闡明瞭清單部分。
下圖展現了用在.NET文檔和書籍中的關於程序集標識符的一些術語。
強命名(strongly named)程序集有一個惟一的數字簽名依附於它。強命名程序集比沒有強名稱的程序集更加安全,緣由有如下幾點。
弱命名(weakly named )程序集是沒有被強命名的程序集。因爲弱命名程序集沒有數字簽名, 它天生是不安全的。由於一根鏈的強度只和它最弱的一環相同,因此強命名程序集默認只能訪問其餘強命名程序集(還存在一種方法容許「部分地相信調用者」,但本書不作闡述)。
程序員不產生強名稱。編譯器產生它,接受關於程序集的信息,並散列化這些信息以建立一個惟一的數據簽名依附到該程序集。它在散列處理中使用的信息以下:
在圍繞強名稱的命名法方面有一些差別。本書所指的「強命名的」常指的是「強名稱的」。 「弱命名的」有時指的是「非強命名的」或「帶簡單名稱的程序集」。
要使用Visual Studio強命名一個程序集,必須有一份公鑰/私鑰對文件的副本。若是沒有密鑰文件,可讓Visual Studio產生一個。能夠實行如下步驟。
在編譯代碼時,編譯器會生成一個強命名的程序集。編譯器的輸入和輸出以下圖
要建立強命名程序集還可使用Strong Name工具(sn.exe ),這個工具在安裝Visual Studio 的時候會自動安裝。它是個命令行工具,容許程序員爲程序集簽名,還能提供大量管理密鑰和簽名的其餘選項。若是Visual Studio IDE還不符合你的要求,它能提供更多選擇。要使用Strong Name工具,可到網上查閱更多細節。
在目標機器上部署一個程序就像在該機器上建立一個目錄並把應用程序複製過去同樣簡單。若是應用程序不須要其餘程序集(好比DLL),或若是所需的DLL在同一目錄下,那麼程序應該會就在它所在的地方良好工做。這種方法部署的程序集稱爲私有程序集,並且這種部署方法稱爲複製文件(XCopy)部署。
私有程序集幾乎能夠被放在任何目錄中,並且只要它們依賴的文件都在同一目錄或子目錄下就足夠了。事實上,能夠在文件系統的不一樣部分有多個目錄,每一個目錄都有一樣的一組程序集,而且它們都會在它們各自不一樣的位置良好工做。
關於私有程序集部署的一些重要事情以下:
私有程序集是很是有用的,但有時你會想把一個DLL放在一箇中心位置,這樣一個單獨的複製就能被系統中其餘的程序集共享。.NET有這樣的貯藏庫,稱爲全局程序集緩存(GAC)。放進GAC的程序集稱爲共享程序集。
關於GAC的一些重要內容以下:
當試圖安裝一個程序集到GAC時,CLR的安全組件首先必須檢驗程序集上的數字簽名是否有效。若是沒有數據簽名,或它是無效的,系統將不會把它安裝到GAC。
然而,這是個一次性檢査。在程序集已經在GAC內以後,當它被一個正在運行的程序引用時,再也不須要進一步的檢査。
gacutil.exe命令行工具容許從GAC添加或刪除程序集,並列出GAC包含的程序集。它的3個最有用的參數標記以下所示。
/i
: 把一個程序集插人GAC/u
: 從GAC卸載一個程序集/l
: 列出GAC中的程序集在程序集部署到GAC以後,它就能被系統中其餘程序集使用了。然而,請記住程序集的標識符由徹底限定名稱的所有4個部分組成。因此,若是一個庫的版本號改變了,或若是它有一個不一樣的公鑰,這些區別指定了不一樣的程序集。
結果就是在GAC中能夠有許多不一樣的程序集,它們有相同的文件名。雖然它們有相同的文件名,但它們是不一樣的程序集並且在GAC中完美地共存。這使不一樣的應用程序在同一時間很容易使用不一樣版本的同一DLL,由於它們是帶不一樣標識符的不一樣程序集。這被稱爲並肩執行(side-by-side Execution )。
下圖闡明瞭GAC中4個不一樣的DLL,它們都有相同的文件名 MyLibary.dll。圖中,能夠看出前3個來自於同一公司,由於它們有相同的公鑰,第4個來源不一樣,由於它有一個不一樣的公鑰。這些版本以下:
配置文件含有關於應用程序的信息,供CLR在運行時使用。它們能夠指示CLR去作這樣的事情,好比使用一個不一樣版本的DLL,或搜索程序引用的DLL時在附加目錄中查找。
配置文件由XML代碼組成,並不包含C#代碼。編寫XML代碼的細節超出了本書的範圍,但應當理解配置文件的目的以及它們如何使用。它們的一種用途是更新一個應用程序集以使用新版本的DLL。
例如,假設有一個應用程序引用了GAC中的一個DLL。在應用程序的清單中,該引用的標識符必須徹底匹配GAC中程序集的標識符。若是一個新版本的DLL發佈了,它能夠被添加到GAC中,在那裏它能夠幸福地和老版本共存。
然而,應用程序仍然在它的清單中包括老版本DLL的標識符。除非從新編譯應用程序並使它引用新版本的DLL,不然它會繼續使用老版本。若是這是你想要的,那也不錯。
然而,若是你不想從新編譯程序但又但願它使用新的DLL,那麼你能夠建立一個配置文件告訴CLR去使用新的版本而不是舊版本。配置文件被放在應用程序目錄中。
下圖闡明瞭運行時過程當中的對象。左邊的應用程序MyProgram.exe調用MyLibrary.dll的1.0.0.0版,如點化線箭頭所示。但應用程序有一個配置文件,而它指示CLR加載2.0.0.0版。注意配置文件的名稱由執行文件的全名(包括擴展名)加上附加擴展名.config組成。
公司當心地保護它們官方的公鑰/私鑰對是很是重要的,不然,若是不可靠的人獲得了它,就能夠發佈假裝成該公司的代碼。爲了不這種狀況,公司顯然不能容許自由訪問含有它們的公鑰/私鑰對的文件。在大公司中,最終程序集的強命名常常在開發過程的最尾部由特殊的有密鑰訪問權限的小組執行。
但是,因爲個別緣由,這會在開發和測試過程當中致使問題。首先,因爲公鑰是程序集標識符的4個部分之一,因此直到提供了公鑰它才能被設置。並且,弱命名的程序集不能被部署到GAC。開發人員和測試人員都須要有能力編譯和測試該代碼,並使用它將要被部署發佈的方式,包括它的標識符和在GAC中的位置。
爲了容許這個,有一種修改了的賦值強命名的形式,稱爲延遲簽名(delayed signing)或部分簽名(partial signing),它克服了這些問題,並且沒有釋放對私鑰的訪問。
在延遲簽名中,編譯器只使用公鑰/私鑰對中的公鑰。而後公鑰能夠被放在完成的程序集的標識符清單中。延遲簽名還使用一個爲0的塊保留數字簽名的位賈。
要建立一個延遲簽名的程序集,必須作兩件事情。第一,建立一個密鑰文件的副本,它只有公鑰而不是公鑰/私鑰對。下一步,爲程序集範圍內的源代碼添加一個名稱爲 DelaySignAttribute 的附加特性,並把它的值設爲true。
下圖展現了生成一個延遲簽名程序集的輸人和輸出。注意圖中下面的內容。
若是你試圖部署延遲簽名的程序集到GAC,CLR不會容許,由於它不是強命名的。要在這臺機器上部署它,必須首先使用命令行指令取消在這臺機器上的GAC簽名確認,只針對這個程序集,並容許它被裝在GAC中。要作到這點,從Visual Studio命令提示中執行下面的命令。
sn -vr MyAssembly.dll
如今,你已經看到弱命名程序集、延遲簽名程序集和強簽名程序集。下圖總結了它們的結構區別。
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">