2004.12.21 歐岩亮
課程介紹
深入Microsoft .NET Framework
基礎內容
熟悉.NET
課程內容
程式的託管執行(Managed Execution)
程式集(Assemblies)
名稱空間(Namespaces)
委託(Delegates)
線程
應用程式定義域(AppDomains)
Framework類
程式中的屬性(Attribute)
資料類型
反射(Reflection)
Framework編程
託管執行
Managed 程式碼和Unmanaged 程式碼
Managed 程式碼是第一次編譯形成中間代碼以後的代碼,它在進入執行的時候,要進行第二次編譯。它不是電腦上的本地代碼,而是一種中間形式的代碼。
Managed 程式碼和Unmanaged 程式碼的本質區別:一個是中間代碼來表示;一個是本地的機器語言。
通用語言執行平台(Common Language Runtime,CLR)
當Managed 程式碼被編譯器第一次編譯以後,在應用程式被載入的時候,通用語言執行平台會提供一些類的載入庫。把Managed 程式碼中的程式載入到記憶體之後,它會通過JIT即時地把Managed 程式碼編譯為本地代碼然後執行。
中繼語言(Intermediate Language,IL)
經過編譯器第一次編譯以後形成的應用程式集應該是用中繼語言來表示的Managed 程式碼,在真正執行的時候,CLR會把Managed 程式碼載入到記憶體當中並且進行Just-In-Time 編譯。這種Managed 程式碼其實是多平台一致的,但是在真正啟動並執行時候,會根據當前平台上的一些代碼的特性進行Just-In-Time 編譯形成本地代碼。
ILDASM
這個工具可以用來反編譯Managed 程式碼。
公用類型系統
保證了多語言的互操作。
記憶體管理
非確定性記憶體回收:當我們把指標設定為null的時候,指標這時並沒有被釋放,因為有記憶體回收機制。
記憶體回收:周期性地回收沒有被指標指向的記憶體空間。
IDispose:給開發人員一定的機會來回收Unmanaged 程式碼中消耗的資源。託管資源的記憶體由記憶體回收自動回收,而在Managed 程式碼中引用的非託管對象,記憶體回收是不負責回收的,例如檔案控制代碼,當我們要使用檔案控制代碼時,我們一定要實現IDispose介面來主動回收資源。
Managed 程式碼的執行過程
代碼第一次編譯形成IL中繼語言的Managed 程式碼,在運行時被Class Loader裝載後進行JIT第二次編譯形成託管的本地代碼。在執行過程中,它會不斷地檢查當前我們執行的代碼的安全性和規範性。
Class Loader在裝載可執行程式exe或者動態連結程式庫dll的時候,它不是把所有的exe和dll當中的類庫全部裝載到記憶體裡面。它是先裝載一部分,即Main函數所在的檔案,然後在執行過程中Class Loader會不斷地判斷當前執行過程中所要調用的方法是否已被裝載到記憶體中了,如果沒有,它會即時地去裝載一些沒有被裝載的代碼。裝載進來之後,被編譯為託管的本地代碼,然後調用。
通用語言執行平台(CLR)
線程支援
類型檢查
安全引擎
MSIL到本地編譯
代碼管理器
記憶體回收
類裝載器
COM Marshaller:COM組件的互操作
異常管理
調試器
公用類型系統
建立了一個架構,能夠協助實現不同語言之間的互操作,型別安全和高效能的代碼執行
提供了一套統一的物件導向模型,可以完全的支援所有的語言
定義的一套語言規範,能夠協助不同語言之間進行互動
程式集(Assemblies)
一個程式集是一群組類型和資源的集合,共同組成一定的邏輯功能
包含一個類型或程式的清單(manifest),類型原資料,MSIL,資源
資訊清單描述了應用程式集裡面都有哪些類型,這些程式都存放在什麼地方。它規定了我的這些代碼都在什麼地方,可能在磁碟的某個位置,另外的一部分資源可能在Internet上。Manifest最終被Class Loader所用,Class Loader在動態載入類庫的時候,就需要知道類庫在哪個位置的哪個檔案裡。我們在編寫應用程式的時候,實際上我們可以把資訊清單裡面的Assembly的位置描述為在一個互連網上的Http地址,Class Loader在即時地載入這個類的時候它會從Http這個路徑去載入遠程伺服器上的Library過來,這楊就實現了零部署,我們不用在更新了Dll之後強迫用戶端更新。
所有能夠部署的單元都是編譯過的MSIL(可執行檔中間代碼)
輕便的可執行檔(PE file)EXE或者是DLL
在.NET Framework程式在執行的時候,它有一個公用類型系統,這些公用類型系統的實現都在CLR裡面。我們在運行程式的時候,使用到了大量的Framework中所定義好的類庫,而用戶端在執行應用程式的時候實際上已經安裝了Framework,也就是說它已經幫我們在用戶端部署了很多已經存在的功能。我們的程式在完成後,有很多類庫不需要封裝在我們的執行檔案裡面,我們只需要把執行檔案放到用戶端,去調用用戶端的Framework裡面的類庫。這樣我們編寫的應用程式規模就很小,但它執行起來記憶體佔用量比較大。
可以用ILDASM和反射(Reflection)來檢查程式集
可以是單一的檔案或多個檔案
名稱空間
名稱空間是一個命名的容器
名稱空間可以按照層次的方式來組織類
避免命名衝突
協助提示類的用途
名稱空間可以跨越工程/程式集
推薦:CompanyName.Project<.Module>.Class
舉例:Northwind.OrderEntry.Order
示範一
程式集和名稱空間
Delegates
Delegate實際上是.NET中的類,是一個強型別的函數指標
主要用於事件處理和回調
多播的Delegate:Combine和Remove方法可以添加或者去除Delegates中的調用列表
可以通過Invoke來調用Delegate指向的方法
可以使用Delegate來完成非同步呼叫,BeginInvoke和EndInvoke方法
Delegate的實現是運行時提供的,使用者不用關心
Delegate在運行時決定調用怎樣的使用者代碼,使用者需要編寫這些代碼
示範二
Delegates
運行結果
其中932是介面線程的ID,1380是非同步呼叫時的線程ID。也就是說BeginInvoke為我們建立了一個線程來執行我們函數指標所指向的函數。
這樣做有一些好處,SimpleDelegateHandler方法位於MainClass類內部,它能訪問MainClass裡面的一些私人成員,而DelegateExample類不能訪問,因此將SimpleDelegateHandler方法傳入DelegateExample類的CallMeBack函數,就可以完成調用。也就是說CallMeBack裡面如果想去更新或者設定MainClass裡面的私人成員,我們可以在MainClass裡建立一個Delegate,通過外部的DelegateExample類裡的CallMeBack,做一個回調,回調到SimpleDelegateHandler方法裡,在這個方法裡就能訪問MainClass裡的私人成員。
線程
具有優先順序的多任務作業系統——“時間片”將刻度分配給多個線程
多線程技術可以在背景工作執行緒執行長時間計算的同時,相應使用者的UI操作
System.Threading.Thread類實際上描述的就是一個系統線程
ThreadPool.QueueUserWorkItem()可以非同步地執行一些操作,通過使用系統的線程池來完成
將你使用的線程數量降低到最少!
AppDomains
AppDomain是一個獨立的應用程式運行環境
它是一個邏輯空間,是線程與進程之間的一種東西。一個.NET進程出現了以後,這個.NET進程會去裝載必要的東西,然後建立一個AppDomain,在AppDomain裡面實現多線程。
AppDomain在執行Managed 程式碼時提供分離應用程式的能力、卸載應用程式的功能和安全邊界
舉個例子,當我們的Hello World程式載入的時候,.NET Framework的CLR會為我們建立一個SystemDomain,這個SystemDomain會去裝載mscorlib,即微軟的核心運行庫。這個核心運行庫運行在SystemDomain裡面,還會建立一個SharedDomain,SharedDomain會去裝載GAC中所具有的程式集。最後建立一個Domain0,來裝載介面線程,或主線程。
所有的Managed 程式碼都在AppDomain中執行
在一個進程中可以執行多個應用程式定義域
應用程式定義域與線程之間沒有一一對應的關係
一個應用程式定義域可以擁有多個線程
一個線程可以在一個應用程式定義域中運行,同時也可以在多個應用程式定義域之間運行
示範三
AppDomain和線程
在儲存按鈕被點擊的時候,是在主線程內被點擊的,在主線程裡面建立了一個新的線程,在新的線程裡面去執行EmulateReallyLongProcess。在這個方法裡面又建立了一個線程,去非同步呼叫DoneDelegateHandler方法。這裡一共會出現三個線程。
這個例子同上面的例子類似,當一個表單裡的按鈕被按下,我們可以讓另外一個線程去工作,但是同時當另外一個線程工作的時候,我們又希望更新當前表單的介面元素,但是我們這個方法又不在當前介面的內部。即EmulateReallyLongProgress方法想要更改Form中的元素的話,我們只能通過Delegate這種方式來進行回調,回調之後就可以訪問類的私人成員。
Framework類庫
公用名稱空間
System
System.Data
System.Data.Xml
其他的名稱空間
System.Windows.Forms
System.Web
System.Configuration
System.IO
System.Security
程式的屬性(Attribute)
Attribute是一些.NET的類,它可以在IL中添加一些原資料,來描述程式的一些屬性,這些屬性是在編譯的過程中被識別的
屬性可以被用來描述下面任何一種類型:Assembly、Module、Property、Field、Event、Interface、Parameter、Delegate、ReturnValue
Attribute可在運行時通過編寫代碼進行尋找,並且可以通過Attribute來控制碼的執行
在Framework中內嵌了很多Attribute
也可以編寫一些自己的Attribute,只需從System.Attribute派生即可
CLSCompliantAttribute
用來指定當前的程式或代碼是否符合通用語言執行平台的規範(CLS)
這個標籤(Attribute)只對程式集、模組、類型或類型的成員起作用
如果不使用CLSCompliantAttribute標籤的話,編譯器預設:
程式集是CLS不相容的
如果某個類型是CLS相容的,那麼它所包含的所有類型或程式集都必須是CLS相容的
如果某個類型的成員是CLS相容的,那麼這個成員的類型必須是CLS相容類型
資料類型
參考型別
必須被執行個體化才能使用
類
在託管堆中分配
實值型別
沒有預設的建構函式(不需要執行個體化)
簡單類型(Primitives)——Integer、float、string、byte
結構
在棧中進行記憶體配置
示範四
資料類型
C#裡面是區分大小寫,第一個函數和第三個函數唯一的區別就是大小寫不同。這些方法在C#環境下被編譯為IL之後,這三個函數能做有效區分。但是這三個函數如果是在VB環境下編譯的話,VB是不區分大小寫,就會報錯。所以像這種寫法不是CLS相容的寫法。
當我們把AssemblyInfo裡面的CLSCompliant標籤加上時,也就是我們要求CLS相容,即使在C#環境裡面也會報錯。
可以看到出現了四個錯誤,主要錯誤有:UInte32類型不是CLS相容的;兩個函數只有大小寫不一樣不是CLS相容的,因為有些語言是不區分大小寫。
版本(Versioning)
在程式集中使用強命名
使用工具sn.exe可以產生一個強命名的(strong-name)密鑰檔案(存有金鑰組)
密鑰檔案裡麵包含了金鑰組(公開金鑰和私密金鑰),當應用程式編譯並簽名的時候,從密鑰檔案中拿出私密金鑰,對應用程式集簽名實行一定的反列。反列之後,應用程式具有了一定的特徵,因為別人沒有你的私密金鑰,只有你才能進行簽名。然後把sn產生的密鑰檔案的公開金鑰儲存在應用程式集中,當其他程式集來調用這個強命名的程式集時,它會用附帶在應用和程式集中的公開金鑰來驗證私密金鑰簽名的有效性。
使用AssemblyKeyFile標籤可以將強命名的密鑰編譯到應用程式集當中
使用強命名可以用來確認程式集的版本,並將程式集安裝到Global Assembly Catch(GAC)當中
示範五
Versioning
使用sn -k命令
產生密鑰成功
使用強命名
當一個應用程式集被強命名的時候,它同時要求它所引用的其他程式集也要是強命名的。
強命名實際上是保證了代碼不被竄改。一旦代碼被竄改,也會保證代碼版本的有效性。
當我們把版本號碼中的*改成固定值時
並且要求主專案對子項目的引用是直接從路徑中選擇dll檔案引用,而不是Project項目引用。(因為如果是Project項目引用,VS也會自動替我們更新整個程式集的版本)
當編譯成功之後,我們再只把一個子項目的版本改為1.0.0.1
只編譯這個子項目成功之後,不編譯主專案,我們把編譯好的1.0.0.1版本的子項目的dll替換主專案的bin檔案夾下的1.0.0.0版本的dll。
然後我們直接執行應用程式,並使用裡面的那個我們更新版本的子項目的功能,這個時候就會出現應用程式集的版本不正確的錯誤資訊。
反射(Reflection)
System.Runtime.Reflection名稱空間
反射可以用來在運行時擷取中繼資料資訊
找出PE檔案(程式集)中的類型和中繼資料
在運行時動態使用類型
示範六
反射
這是一個簡單的About表單
Assembly.GetExecutingAssembly可以獲得當前執行的應用程式集,然後通過GetCustomAttributes可以獲得當前應用程式集的資訊。這些應用程式集資訊都在AssemblyInfo裡面設定。
這樣在運行時About表單就能讀出相應程式集資訊。
其中列表顯示了當前啟動並執行其他程式集資訊,這是通過下面的代碼擷取的
GetEntryAssembly得到當前啟動並執行所有載入的程式集,GetReferenceAssemblies得到當前應用的程式集。
Framework編程
Framework類庫
Collections
Arrays
Enums
IEnumerable介面和Foreach(For Each)
只要實現了IEnumerable介面,就能使用Foreach遍曆成員
運行時資訊
反射
程式集屬性、標籤
AppDomain
字串
字串的內容是固定的,immutable(不可變更)
即字串一旦形成,它便不可以改變
串連少量的字串,建議使用String.Format()
若使用大量的+去連接字串,效能會有損失
使用System.Text.StringBuilder來串連大量的字串
StringBuilder的AppendFormat()方法
示範七
字串
帶輸出的字串串連,使用System.Diagnostics.Trace.WriteLine可以很方便的輸出我們的調試資訊,這個尤其是在多線程調試的時候有用,因為多線程在設定斷點的時候可能會有些問題。
Main函數
輸出面板
總結
.NET Framework包含了一系列有層次的用名稱空間劃分的可重用組件
MSIL,PE
Attribute可以定製程式集當中各種元素的中繼資料,或描述資訊
反射可以在運行時獲得程式集當中的類型,可以方便的實現外掛程式的功能
2010.10.7