跳轉到內容

.NET 開發基金會/AllInOne

來自華夏公益教科書


Microsoft .NET Framework 2.0
應用程式開發基礎

歡迎來到華夏公益教科書的 Microsoft 認證考試 70-536 學習指南的“多合一”檢視頁面。考慮到它的規模,此頁面沒有配置為編輯(請使用 主頁)。


前言

歡迎使用“Microsoft .NET Framework 2.0 應用程式開發基礎”模組。

本書對與 Microsoft 認證計劃 的考試 70-536 相關的考試目標進行了廣泛的“教科書式”覆蓋。這些目標涵蓋了基本 .NET 類庫以及程式與 .NET 執行環境的互動。

請告訴我們您對該模組的反饋,以便我們對其進行改進(請參見 討論頁面)。

前言包含與文件上下文、作者、華夏公益教科書等相關的資訊。如果您主要對模組內容感興趣,可以跳過到 簡介

受眾和其他華夏公益教科書資源

本學習指南的受眾是專業從事 .NET 框架的軟體開發人員。Microsoft 在考試(70-536)準備指南 中指出:“考生應至少擁有兩年到三年使用 .NET Framework 1.0、.NET Framework 1.1 和 .NET Framework 2.0 開發基於 Web 的、基於 Microsoft Windows 的或分散式應用程式的經驗。考生應熟練掌握 Visual Studio 2005。”。

對於本學習指南,我們只假設讀者至少了解一種 .NET 語言(VB.NET、C# 等),可以使用 Visual Studio,並且具有一定的使用經驗。

請注意,.NET 平臺非常廣泛,並且還假設您對計算機科學有基本瞭解。例如,我們將簡要討論面向物件原則如何在框架中實現(從開發人員的角度),但不會討論原則本身,它們來自哪裡,它們解決了哪些具體問題等等。

華夏公益教科書有其他關於 VB、C# 和 .NET 的書籍,這些書籍更具入門性質。

最後,我們所說的“專業感興趣”指的是,本書和其他認證學習指南對框架進行了廣泛的介紹,並且需要大量的學習時間。所以任何想要獲得這種介紹並且有時間的人都可以加入進來!

貢獻

此模組遠未完成!請隨時根據需要改進它。

如果您是華夏公益教科書的新貢獻者,這裡有一些提示

  • 華夏公益教科書是關於教科書,而不是“標準”維基。專案團隊正在尋找可以從頭到尾閱讀的連續敘事。對於習慣了 MSDN 型別文件的人來說,這可能是一個很大的調整。
  • 只包含連結的頁面不符合上述圖片,因此應積極避免。如果您建立了一個頁面,請確保在上面新增內容 :-)
  • 管理員使用模板來詢問問題或通知未完成的任務。這些模板會產生相當醒目的通知,乍一看可能會讓人驚訝。不要擔心外觀,並提出澄清問題。管理員非常樂於助人,並且會盡力幫助您解決任何問題。
  • 程式碼示例非常受歡迎,應放在“隱藏”部分中,以保留文字的流程。請參見 示例部分。示例應儘可能簡短,但印刷材料的空間限制顯然在這裡不適用。因此,我們鼓勵您使用完整的、簡潔明瞭的程式,以便可以對其直接測試和使用。

考試資訊和認證

有關考試的最新資訊可以在 MSDN 考試資訊 中找到。

此模組是獲得許多 Microsoft 認證的第一個考試(70-536)的學習指南。

作者

William “Scott” Baker (使用者:Scott98390)

如果您對本書進行貢獻,並且願意的話,請在這裡新增您的姓名。特定文章的貢獻者可以透過其歷史記錄追溯。

請注意,華夏公益教科書的政策不是建立只包含連結的頁面或文字非常少的頁面。首選的方式是擁有可以從頭到尾閱讀的連續教科書。對該模組的最初貢獻是為每個三級或四級考試目標建立單獨的頁面。這導致了大量頁面被合併到更一致的全域性頁面中。對於只包含連結的頁面,這個過程產生了負面影響,即失去了對這些頁面的貢獻的引用。對於包含文字的頁面,更改歷史記錄已遷移到合併的頁面中。


簡介

模組目標

本學習指南旨在替代其他用於準備透過 70-536 考試的資源。

  • 我們提供了指向 MSDN 庫的連結,但並沒有試圖取代它。透過考試的人經常指出,培訓工具包 沒有涵蓋考試的各個方面,並且查閱 MSDN 文件是一個很大的優勢。
  • 我們提供了指向維基百科和其他維基媒體專案的連結,這些連結在適用時會使用。這不是一系列理論或百科全書文章,而是關於如何在 .NET 框架中實現這些概念以及如何使用它們的指南。
  • 我們不提供測試軟體
  • 我們不假裝此模組可以替代 Microsoft 推薦的培訓工具包或任何其他推薦材料。

但是,即使是在此文件極其不完整的狀態下,我們也提供了以下內容:

  • Microsoft 列出的考試目標的詳盡列表
  • 從所有考試目標到相應 MSDN 庫文章的連結。
  • 提供越來越多的主題的“教科書式”解釋,並連結到相關的外部資源。
  • 最重要的是,它提供了一個地方,讓你可以在考試前存放重要的筆記和程式碼示例,以便複習,並在考試後作為專業工作參考。沒有哪個培訓工具包或庫能在共享和受控的環境中提供這種功能。

像這樣一本書最難的部分是保持平衡,既要有足夠的篇幅來涵蓋和解釋考試的每個目標,又不能太多,因為閱讀和學習時間應該保持在可控的水平。此外,沒有必要在這裡涵蓋與考試目標無關的內容。

最後,作為維基媒體家族(維基百科等)的一部分,華夏公益教科書專案在版權和總體質量方面擁有非常高的道德標準。如果你發現任何地方有任何“錯誤”,請不要猶豫,進行更正。

模組結構

本模組圍繞微軟為 70-536 考試設定的目標而構建。第 3 章到第 9 章代表考試的 7 個主要目標類別。

堅持考試“官方”目標的理念是,我們假設認證提供者(微軟)對產品(.NET)有一定的瞭解,並且可以說明哪些是需要了解的重要內容。

對於每一章

  • 第一部分涵蓋本章中討論的主要概念(主題)。這個“主題”部分允許我們將“是什麼”與“怎麼做”分開。請注意,考試側重於庫和工具的使用,僅僅瞭解概念是不足以應付考試的。
  • 第二部分詳細介紹該目標類別中每個二級、三級和四級考試目標(怎麼做、詳細用法和 MSDN 庫的參考)。

最終,我們希望擁有

  • 每一章的“複習問題”部分,在那裡我們可以討論考試中可能會問到的問題型別。
  • 一個“高階”部分,在那裡我們可以放置更高階的材料,並將基本部分的文字流保持在與考試要求的知識水平相符的水平。

截至 2007 年 12 月 7 日

  • 24 個主題在單獨的頁面上詳細介紹(主題標題是連結)。大多數主題最終將被整合到主頁面,以實現更好的文字流。
  • 所有主題都直接連結到微軟軟體開發者網路 (MSDN) 庫,大約 480 個主題直接從主頁面連結(標題後面的“MSDN”是連結),其他主題則從各自的子頁面連結。
  • 我們剛剛開始將“主題”部分連結到維基百科文章,讓你瞭解這些概念是如何在微軟世界之外定義和處理的。

有些部分比較高階,另一些部分正在等待你的貢獻。更高階的部分並不都在模組的開頭。

.NET Framework

在包含考試 70-536“Microsoft .NET Framework 2.0 應用程式開發基礎”的認證路徑中,它代表了認證流程的第一步。因此,從整體上簡要討論框架,作為這份第一個學習指南的開始是自然而然的。

MSDN .NET 主頁 上的定義是:“.NET Framework 是微軟的託管程式碼程式設計模型,用於在 Windows 客戶端、伺服器以及移動或嵌入式裝置上構建應用程式。開發人員使用 .NET 構建各種型別的應用程式:Web 應用程式、伺服器應用程式、智慧客戶端應用程式、控制檯應用程式、資料庫應用程式等等。”

維基百科的定義是:“Microsoft .NET Framework 是包含在 Microsoft Windows 作業系統中的軟體元件。它提供了大量預先編碼的解決方案來滿足常見的軟體開發需求,並管理為框架專門編寫的程式的執行。.NET Framework 旨在被 Windows 平臺上建立的大多數新應用程式使用。”

微軟定義的問題在於它提到了“託管程式碼程式設計模型”,這仍然是微軟的術語。對此最好的定義是:“託管程式碼是指其執行由 .NET Framework 公共語言執行時管理的程式碼”(參見 Brad Adams 在 MSDN 上的部落格)。

維基百科的定義指出了從開發人員的角度來看的兩個更重要的方面

  • 存在一個龐大的類庫集合,可以完成所有常見的程式設計任務。除了這些類庫的龐大規模,它們還在快速發展。
  • 執行環境是特定於框架的。術語“虛擬機器”通常用於描述這種環境。

這兩個主要特徵與 Java 環境類似,Java 環境是 .NET 框架的主要競爭對手。本模組旨在幫助你學習 .NET 框架。它不會涉及 .NET 與 Java 的比較和討論。

框架結構

公共語言基礎設施 (CLI) 的可視概述

右側的圖片來自維基百科關於 .NET 框架的文章(見上文)。

它描述了用 .NET 相容語言編寫的程式從原始碼到執行的過程。與傳統程式語言(例如 C++)的區別在於程式要編譯兩次。第一次編譯是從原始語言編譯到“通用中間語言”(CIL)。這實際上是“進入”程式集的內容。

第二次編譯是從 CIL 編譯到機器程式碼。這種編譯通常由“即時”(JIT)編譯器自動執行。也可以使用原生映像生成器 (ngen) 實用工具在執行時之前建立機器程式碼。

這種架構對本書的主題有許多直接的影響。其中包括

  • 第二次編譯的“原始碼”(CIL 程式碼)始終在執行時可供虛擬機器使用。這意味著可以在執行時輕鬆分析此程式碼,與傳統的編譯環境相反。這個特性是平臺反射功能的基礎。我們不僅可以分析“中間原始碼”,還可以實際在執行時建立(發出)一些程式碼,然後進行“即時”編譯和執行。
  • 執行在 CLR 的上下文中完成(這就是“託管”程式碼的概念)。換句話說,我們可以說執行時始終“知道”它要傳送執行的內容,與傳統環境不同,在傳統環境中,程式碼直接由作業系統執行。這意味著你可以告訴執行時執行或不執行某種型別的程式碼。這是程式碼訪問安全的依據。
  • .NET 語言(C#、VB 等)的大多數功能都與中間語言的功能直接相關。換句話說,大多數情況下,第一次編譯非常簡單。但是,有些結構在 CIL 中沒有直接的等價物(例如,C# 中的屬性在 CIL 中對映到類方法)。

我們可以繼續這樣討論很長時間。我們想在這裡說明的是,開發人員可以從對平臺的詳細瞭解中獲益良多,即使它沒有直接被列為考試目標。

如果你想在微軟文件中閱讀有關框架的資訊,可以檢視 MSDN

最後我們要指出的是,公共語言執行時 (CLR) 可以執行在不同的上下文中

  • ASP.NET 用於 Web 應用程式,它直接連結到 Internet 資訊服務 (IIS)。
  • Internet Explorer 用於客戶端 Web 控制元件
  • 在用於控制檯、服務和 Windows 應用程式的獨立主機中。

預設情況下,本書中的示例將使用獨立的可執行檔案。這只是因為對於非常簡單的程式,它們更容易部署。這並不意味著 Windows 應用程式比 Web 應用程式更可取。

本書的定位

.NET 堆疊

右側的圖片,也來自維基百科,簡化但清晰地展示了框架的功能元件。

本書(以及相關的考試)涉及“基類庫”元件和程式與“公共語言執行時”元件的基本關係。

本書不涵蓋 ASP.NET、ADO.NET、WF 或任何其他更專業的元件。

我們可以說,我們廣泛地涵蓋了基礎知識,但淺顯地涵蓋了框架本身。

與公共語言執行時的關係包括以下內容

  • 部署
  • 安全
  • 配置
  • 反射
  • 服務
  • 與 WMI 的關係
  • 互操作性
  • 等等。

基類庫包括

  • 型別
  • 集合
  • 泛型
  • 文字操作
  • 基本繪圖
  • 基本全球化
  • 序列化
  • 基本輸入/輸出
  • 多執行緒
  • 等等。

總而言之,它們都是比較“枯燥”的主題。從廣泛地涵蓋基礎知識開始,這對於初學者來說肯定不是最有趣的學習模式。因此,我們建議真正的初學者從更合適的資料開始。

關於框架“堆疊”的最後一點是,新版本傾向於新增新的元件,而不是“重做”現有的元件。這意味著大多數“2.0”基礎知識在“3.5”中仍然有效。然而,一些“基本”功能在新版本中得到了增強或更改。我們會盡量在可能的地方將這些內容作為旁註記錄下來。

程式設計正規化

為了簡單起見(過於簡單?),我們可以說程式設計正規化是程式設計的“風格”,是建模問題並將其解決方案“翻譯”成程式碼的一種特定方式。

從開發人員的角度來看,.NET 框架(及其類庫)是一個通用的面向物件平臺,它不斷擴充套件以支援其他程式設計正規化(使用泛型進行泛型程式設計,使用屬性進行面向方面程式設計,使用 WF 進行響應式程式設計,使用 C# 3.0 中的 Lambda 表示式進行函數語言程式設計等)。這種“擴充套件過程”反映了 Java 的擴充套件過程。.NET 和 Java 平臺是目前唯一支援這種廣泛的多正規化擴充套件過程的兩個平臺。

討論每個正規化實現的範圍和“理論健全性”顯然超出了本書的範圍。

我們在這裡的觀點是,在同一個平臺上瀏覽所有這些程式設計風格可能會讓人感到困惑。維護程式碼也變得越來越困難,因為兩個最初的程式設計師可能會使用截然不同的“風格”或結構來解決同一個問題。

因此,我們將盡可能地將不同的 .NET 概念與其各自的“風格”聯絡起來,為讀者提供一些背景資訊。

程式集

根據 MSDN,程式集是:“.NET 框架應用程式的構建塊;它們構成了部署、版本控制、重用、啟用範圍和安全許可權的基本單元”。

.NET 框架現在是微軟在 Windows 平臺上開發應用程式的首選方式。在這方面,它取代了元件物件模型 (COM)。這很重要,因為儘管存在一些部署和管理問題(還記得 DLL Hell 嗎?),但 COM 在基於元件的計算(重用整個可執行元件,而不僅僅是程式程式碼)的發展中發揮了重要作用。人們投入了大量精力將 COM 的基於元件的概念移植到 .NET 框架中,本書的很大一部分內容都涉及這些概念(安全性、安裝、版本控制等)。

程式集是 COM 元件的繼承者。


Clipboard

待辦事項
這裡將列出程式集主要特徵的簡短列表。


系統型別和集合

考試目標:開發使用系統型別和集合的應用程式。


主題

程式、物件和方法

.NET 本質上是一組用於構建和執行計算機程式的工具。計算機程式是提供給計算機的一組指令,這些指令會自動執行這些指令以操作某些資料。.NET 遵循面向物件正規化,這意味著指令和資料圍繞表示事物或概念的“物件”進行分組。共享相同指令並操作相同型別資料的物件被分組到同一型別中。

面向物件程式是一系列型別的定義。對於每種型別,都會指定資料型別和指令。指令被分組到方法中。可用的指令之一是建立已定義型別的一個物件。另一種指令是請求執行與物件關聯的方法。這稱為呼叫方法。呼叫方法時,會執行其指令,直到方法終止(返回)。方法返回後,呼叫(呼叫)方法會執行其下一條指令(語句)。

啟動程式執行時,系統會建立一個第一個物件並呼叫該物件的方法(這稱為main方法)。在該方法中,通常會建立其他物件並呼叫這些物件的方法。這些方法將呼叫其他方法,建立其他物件,依此類推。隨著被呼叫的方法返回,'’main’’方法最終會完成,標記程式執行的結束。

系統型別

對於經驗豐富的面向物件開發人員來說,本節內容可能顯而易見,但考試中的一些具體目標與型別系統直接相關。

型別是分類語言概念或物件的一種方式。這種分類的組織方式稱為“型別系統”。型別系統也可以以不同的方式對型別本身進行分類。

在 .NET 中對型別進行分類的第一種方式是在框架類庫中的型別(系統型別)和開發人員構建的型別(自定義型別)之間進行區分。

編寫面向物件程式可以看作是定義一個或多個自定義型別的過程。然後將這些型別打包到某種執行單元中(在 .NET 的情況下為程式集)。程式集被編譯然後執行,從某個入口點開始,該入口點將是自定義型別之一的指定方法。

這些自定義型別使用

  • 系統型別來執行“預先程式設計”的指令序列
  • 其他自定義型別

系統型別也打包在程式集中。自定義程式集必須引用系統程式集才能使用系統型別。

在 .NET 中,還有其他方法對型別進行分類。其中之一是根據基於這些型別建立的物件對映到計算機記憶體的方式。這將使我們得到值型別和引用型別。

另一種方式是透過反射類別(類、值型別、介面、泛型等)。

另一種方法是區分執行時直接支援的型別(內建型別)與在類庫或自定義型別中定義的型別。

這些類別也可以相互交叉,這將使我們獲得諸如“內建值型別”或“系統介面”之類的概念。當遇到這種組合時,請注意使用的分類。

名稱空間是組織型別的另一種方式,這樣可以更輕鬆地找到它們。請參閱 此處,瞭解有關名稱空間的討論。

在名稱空間的上下文中,系統型別是包含在 System 名稱空間或其子名稱空間之一中的型別,自定義型別(非系統型別)應使用其他名稱空間。

要了解微軟如何描述 .NET 型別系統,請參閱 MSDN。然後,要概述類庫(系統型別),請參閱 MSDN

事實上,考試的大部分內容都基於如何使用型別庫(系統型別)的常見部分。這就是為什麼考試目標列表(以及本書的目錄)如此之長的原因。

Hello World

對於 .NET 的新手,您可能需要稍作休息,瞭解到目前為止討論的概念是如何在非常簡單的示例中使用的。接下來的概念並不那麼簡單。我們將在此處提供一個這樣的示例 此處

值型別

值型別表示型別系統中值/引用分類的一部分。

值型別的例項直接包含其資料(值)。例如,Int32 區域性變數的記憶體直接分配在堆疊上。

值型別本身分為 3 類

  • 內建值型別
  • 使用者定義的值型別
  • 列舉

請記住,內建型別是執行時直接支援的型別。

它們是任何程式的構建塊,因為它們是機器指令最終作用的物件。其餘的本質上是對這些型別的組合。

除 Object 和 String 外,所有內建型別都是值型別。

內建值型別包括

  • 整數型別(Byte、SByte、Int16、Int32、Int64、UInt16、UInt32 和 UInt64)
  • 浮點型別(Single 和 Double)
  • 邏輯型別(Boolean)
  • 其他型別(Char、Decimal、InPtr 和 UInPtr)

所有內建值型別都在 System 名稱空間中定義(例如 System.Int32),並且在 VB 和 C# 中具有表示它們的關鍵字(例如 C# 中的 int、VB.NET 中的 integer),但 InPtr 和 UInPtr 除外。

旁註

我們在上面提到過,型別分類有時可能很混亂。例如,請注意 System.DateTime 在 培訓工具包(第 5 頁)中被呈現為內建值型別,並且這在 MSDN KB 中未被識別為錯誤。根據 官方規範(第 19 頁),它不是內建型別。混淆源於 培訓工具包 沒有明確區分系統型別(在類庫中)和內建型別(執行時直接處理的基本型別)。

這裡的要點不是吹毛求疵或貶低培訓工具包作者所做的工作。我們只是想指出,在開始編寫程式碼之前,花幾分鐘時間明確區分這些概念可能是值得的。

所有值型別都直接從 System.ValueType 派生(對於內建型別和使用者定義型別),或者間接透過 System.Enum 從 System.ValueType 派生(對於列舉)。

列舉是一種命名一組底層整數型別(帶符號或無符號)值的方式。作為整數型別的限制,它們充當其底層型別。

使用者定義的值型別和列舉都包含系統型別和自定義型別。

System 使用者定義值型別的示例是 System.Drawing.Point,它用於繪圖。

您可以使用特定語言結構(C# 中的 struct、VB.NET 中的 Structure)構建自定義值型別。

System 列舉的示例是 System.Data.CommandType 列舉,它指定表是文字命令、儲存過程呼叫等。

您可以使用特定語言結構(C# 中的 enum、VB.NET 中的 Enum)構建自定義列舉。

有關使用值型別的示例和說明,請參閱此 部分。另請參閱有關 構建使用 使用者定義的值型別的示例。

引用型別

引用型別代表型別系統中值/引用分類的另一部分。

與值型別相反,引用型別的例項不直接包含其資料(值),而是包含對該值記憶體位置的某種引用。例如,一個字串區域性變數在堆疊上分配記憶體用於對包含字串的引用,而不是字串本身。在這種情況下,字串本身將分配在堆上並被垃圾回收(稍後會詳細介紹)。

兩種內建型別被認為是引用型別:物件和字串,它們也直接在系統庫中引用(例如 System.String),並在 .NET 語言中具有自己的構造(例如 C# 中的 string)。

引用型別本身又分為四類:

  • 指標
  • 陣列
  • 介面

本書不會討論指標,因為沒有考試目標涉及它們。

接下來的三節將介紹陣列、類和介面。

有關值/引用型別的比較,請嘗試 這裡

有關其他說明和使用引用型別的示例,請參見 這裡

陣列

陣列本質上是一組物件,通常是相同型別的,可以透過索引訪問。

有關陣列的一般討論,您可以參閱維基百科文章(在右側)或訪問關於 資料結構 的華夏公益教科書。

陣列曾經是程式語言中最常用的特性之一,因為您可以非常高效地從陣列中的一個專案跳轉到下一個專案(如果您想了解更多關於此的詳細資訊,可以檢視 C 指標和陣列)。如今,更多“計算能力”的可用性將重點從陣列轉移到了集合。陣列的兩個主要問題是:

  • 它們是固定長度的
  • 它們只支援一種內部組織

集合解決了陣列的大多數缺點(儘管存在成本)。陣列仍然可以用於必須高效操作的固定組物件。

使用陣列 部分,我們將有一些示例。

類本身又分為三類:

  • 使用者定義的類
  • 裝箱值型別
  • 委託

裝箱值型別將在稍後的 裝箱/拆箱 部分進行討論。

委託將在其 部分 中介紹。

使用者定義的類是面向物件概念的基本實現。

顯然,關於類可以說的很多,但由於沒有考試目標涉及它們,因此我們將假設讀者已經熟悉該概念並已經使用過它們(在 .NET 或其他地方)。

您可以擁有系統類(數百個)和自定義類(您將在其中編寫程式邏輯的基本內容)。

我們有 使用構建 類的示例。

介面

如果您想真正瞭解面向物件程式設計到底是什麼,只需檢視維基百科關於多型性的文章 :-)。

這個想法只是能夠對具有“共同點”的不同型別的“共同部分”使用單一型別的引用。

矩形和橢圓都是“形狀”,那麼我們如何讓程式的一部分操作“形狀”,而無需瞭解矩形或橢圓的具體情況。在這種情況下,我們說形狀是多型的(從字面上講,它們可以採用多種形式)。

在面向物件程式設計中,您可以透過繼承獲得這種行為。對父類(形狀)的引用將毫無問題地操作子物件(矩形或橢圓)。

介面的開發是為了在基於元件的計算環境中獲得相同的效果,在該環境中您無法訪問原始碼,因此無法使用繼承。介面構造是 COM 中所有元件通訊的基礎。

介面是對以明確定義的方式(方法簽名)實現一組方法的契約。如果您知道某個類實現了某個介面,那麼您就知道可以使用的任何已定義的方法。

從這個定義來看,很容易想象有一個對“介面例項”的引用,您可以從中呼叫任何介面方法。事實上,框架確實提供了這一點。

現在假設,而不是形狀作為矩形的父級,我們只有一個形狀介面,它由矩形和橢圓實現。如果我有一個對矩形物件的引用,我將能夠將其“轉換”為對形狀介面的引用,並將其傳遞給只瞭解“形狀物件”的程式部分。

這與我們透過繼承獲得的多型行為完全相同。

類庫嚴重依賴介面,對該概念的清晰理解至關重要。

與繼承相比,介面的一個有趣問題是您不會獲得預設實現(虛擬父方法),因此您必須重新編寫所有內容。這裡確實存在技術和工具,但總是需要做一些額外的工作。更多資訊將在泛型中介紹…

我們有 使用構建 介面的示例。 標準介面 部分展示了 System 名稱空間的一些介面。

屬性

現在,我們簡短地討論一下型別,談談屬性。首先,屬性不是型別。它們是新增到程式元素(程式集、類、方法等)中的資訊元素,以在與該元素關聯的普通程式碼之外對其進行限定。

這些新增的“屬性”用於對底層程式元素進行操作,而無需修改元素的執行邏輯。

我們為什麼要在執行程式碼之外操作程式元素?簡而言之,面向物件的概念難以處理所謂的橫切關注點,或者是對所有或大多數類適用的程式方面。此類方面的示例包括安全性、永續性、序列化等。與其修改每個類以新增序列化邏輯(這與業務規則無關),不如向類新增序列化屬性來指導這些類的序列化過程,而無需更改業務邏輯(執行程式碼)。

框架的許多功能依賴於屬性來向程式元素新增資訊。屬性由編譯過程保留,並在程式集中與其限定元素關聯。它們在執行時使用反射以程式設計方式進行分析,以調整“橫切”功能的行為。

與型別一樣,我們有系統屬性(框架的一部分)和自定義屬性(由開發人員構建)。自定義屬性的開發將在 反射部分 中介紹。在此之前,我們將僅限於系統屬性,重點介紹它們的定義以及它們如何確定框架的行為。

屬性使用部分 將提供一些關於如何使用簡單系統屬性的示例。

在此,我們要注意,屬性不是向程式新增“非執行”資訊以修改其行為的唯一方法。例如,XML 配置檔案可用於指定將影響程式執行的引數。我們將在本書的配置部分討論這些內容。

有關 C# 中屬性的討論,請參見 MSDN

集合

集合是物件的集合。框架具有許多型別的集合,涵蓋了您將物件作為組處理的大多數情況。瞭解這些集合型別可以節省您“重新編碼”等效邏輯的時間,並使您的程式更易於維護。

與陣列不同,集合有多種形式,每種形式都有其特定的內部組織。每種型別的集合都與特定型別的問題相關聯。當我們提供每種型別的集合的示例時,我們將指出問題型別。

我們有兩個包含集合示例的部分:

集合的主要缺點是,在“現實生活中”,分組在一起的物件通常具有某些共同特徵(它們是相同型別,具有共同的父型別或支援共同的介面)。因此,在大多數情況下,我們對物件“瞭解”的不僅僅是它們的組織方式。集合不允許我們使用這些知識來驗證傳遞給集合的物件或適用於集合中所有物件的程式碼邏輯(不進行強制轉換和異常處理)。

框架的 2.0 版本引入了泛型集合。它們在保留集合其他優勢的同時解決了這個問題。因此,應儘可能使用它們,而不是“普通”集合。

有關集合的一些外部連結是 GotDotNetAspNetResources

有關陣列、集合和資料結構的總體討論,請參閱 MSDN

泛型

泛型程式設計或引數化型別的使用不是面向物件的概念。從這個意義上說,泛型有點像屬性,它們被新增到主流面向物件平臺中,以解決面向物件技術難以輕鬆處理的情況。

要開始我們的討論,請閱讀以下有趣的 連結文章(附件),該文章介紹了泛型集合中泛型的概念。此外部 教程 也做了同樣的事情。

泛型的概念很有趣,因為它將泛化的概念應用於型別。型別本身是對物件(面向物件程式設計的基礎)的泛化。因此,我們開始操作型別,即以型別作為引數。

當您將引數型別替換為特定型別時(例如,當您宣告該型別的變數時),將獲得實際型別。

// C#
List<int> myIntList = new List<int>()

'// VB.NET
Dim myIntList As New List(Of Integer)

在 .NET 框架中,這種替換是在第二次編譯(從 CIL 到機器程式碼的即時編譯)期間完成的。換句話說,泛型由 CIL 支援,因此是框架本身的一部分,而不是所用語言的一部分(例如 C#)。

有關泛型的更多資訊,請參閱 MSDN

我們有 使用構建 泛型型別的示例。

有關泛型集合的示例,請參閱 使用泛型集合 部分。

異常

這是丟擲一般異常的方式

// C#
throw new Exception("This is an exception.");

'// VB.NET
Throw New Exception("This is an exception.")

這是處理異常的方式

// C#
try
{
   throw new Exception("This is an exception.");
}
catch (Exception e)
{
  Console.WriteLine(e.Message);
}

'// VB.NET
Try
  Throw New Exception("This is an exception.")
Catch ex As Exception
  Console.WriteLine(ex.Message)
End Try

事件和委託

有關事件和委託的“官方”討論,請參閱 MSDN。我們將在這裡對所涉及的許多概念進行更一般的討論。

第一個概念是委託或將類功能的一部分“委託給”程式中“其他地方”的想法。委託的重要好處是“委託”物件不必知道委託功能是如何實現的。一種“委託”的方式是定義介面。如果一個物件對介面有一個引用,它可以將部分功能委託給實現該介面的物件,而無需瞭解該物件的大量資訊。

.NET 2.0 定義了另一種稱為委託的引用型別,它以稍微不同的方式實現委託模式。委託是一種型別,它定義對必須具有與委託定義相同簽名的單個函式的引用。簽名是對函式引數型別及其返回型別的描述。與類一樣,您可以建立該型別的物件。建立的物件是對函式的引用,該函式可以被分配(將引用設定為特定函式),作為引數傳遞或執行(實際執行被引用的函式)。與介面不同,委託只定義一個函式,並且可以直接建立委託例項(無需擁有實現介面的另一個類)。

.NET 中的大多數委託都派生自多播委託。多播委託是一個委託,它保留對具有與委託定義相同簽名的函式的引用列表,而不是單個引用。可以使用 += 運算子將引用新增到多播委託。當您執行多播委託時,每個引用的函式都會依次執行。

如果一個物件來自實現多播委託成員的類,那麼如果您對該物件有一個引用,您可以將對您選擇的函式的引用“新增到”多播委託中。如果物件決定“執行”其委託成員,那麼您添加了引用的函式將被執行。

這個多播委託成員正是 .NET 中的事件。這就是為什麼事件和委託幾乎總是被一起討論的原因。

定義事件的步驟是

  • 您將委託型別定義為對具有特定簽名的函式的引用
  • 您將事件成員新增到類並將其與您剛剛定義的委託型別關聯,這將例項化與事件關聯的多播委託
  • 當您對類的物件的引用時,您可以將對任何與委託型別具有相同簽名的方法的引用新增進去。
  • 當物件引發事件時,關聯的多播委託將被執行,從而觸發對引用的函式的執行。

大多數時候,您將新增對您自己方法的引用。當引用的物件觸發其事件時,它實際上會執行您的方法之一,而無需知道它。此設定是釋出/訂閱模式的實現。您訂閱事件,表示您希望在發生特定“事件”時被告知。許多物件可以訂閱同一個事件。當引用的物件觸發其事件時,它會“釋出”一條訊息,說明事件已以對所有“註冊”函式的函式呼叫的形式有效地發生。

因此,任何類都可以定義事件。許多系統類定義事件來向您的程式碼傳達“執行環境中發生了某些事情”(滑鼠移動、按下鍵、在套接字上收到訊息等)的事實。構建一個“等待”事件發生並對其做出反應的程式稱為事件程式設計。大多數線上應用程式和服務都遵循這種設計。我們將在多執行緒部分更詳細地討論捕獲系統事件的方式。

有關事件和委託的示例,請參閱 本節


類、介面和工具

Hello World 示例

    using System;
    using System.Collections.Generic;
    using System.Text;
namespace HelloWorldLab1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello world!");
            Console.WriteLine("Press ENTER to exit");
            Console.ReadLine();
        }
    }
}

注意:在 Visual Studio 中建立新的控制檯專案時,預設情況下會生成所有程式碼,除了 Main 方法中的 3 個“控制檯”行。只需新增這 3 行並執行程式。

我們已經討論了該程式的所有部分,除了 Console 的作用,這將在輸入/輸出部分(流)中討論。現在,假設它是一個 System 類,在 System 名稱空間中(“using System”指令使“System.”部分成為“System.Console.Writeline()”可選),以及 WriteLine() 和 ReadLine() 方法寫入並讀取字串到控制檯和從控制檯中讀取字串。



使用系統型別

考試目標:透過使用 .NET Framework 2.0 系統型別來管理 .NET Framework 應用程式中的資料。

(參考 System 名稱空間)

值型別用法

以下是一些關於值型別的“用法”導向的說明。

值型別包含它們被分配的值

int a = 1;  // the variable "a" contains "1" of value type int

值型別也可以透過使用 new 關鍵字來建立。使用 new 關鍵字使用從型別預設建構函式獲得的預設值初始化變數

int a = new int(); // using the default constructor via the new keyword
return a;          // returns "0" in the case of type Int.

值型別可以在未初始化的情況下宣告,但必須在使用之前初始化為某個值

int a;     // This is perfectly acceptable
return a;  // NOT acceptable!  You can't use "a" because "a" doesn't have a value!

值型別不能等於 null。.NET 2.0 提供了 可空型別 來解決此限制,將在 下一節 中討論,但 null 不是值型別中的有效值

int a = null;  // Won't compile - throws an error.

如果將值型別複製到另一個值型別,則值將被複製。更改副本的值不會影響原始值的價值。第二個僅僅是第一個的副本 - 賦值後它們沒有任何聯絡。這是相當直觀的

int var1 = 1;
int var2 = var1;  //the value of var1 (a "1" of type int) is copied to var2
var2 = 25;        // The "1" value in var2 is overwritten with "25"
Console.WriteLine("The value of var1 is {0}, the value of var2 is {1}", var1, var2);

這將導致以下輸出

The value of var1 is 1, the value of var2 is 25

更改副本的值(在本例中為var2)不會影響原始值的價值(var1)。這與引用型別不同,引用型別複製對值的引用,而不是值本身。

值型別不能派生。

值型別作為方法引數預設情況下按值傳遞。值型別的副本被建立,並且副本作為引數傳遞給方法。如果在方法內部更改了引數,則不會影響原始值型別的價值。


Clipboard

待辦事項
最終,我們可以新增一個示例來展示使用一些內建值型別(整數、浮點數、邏輯、字元和小數)以及值型別的值引數傳遞的方式。


可空型別

參閱 MSDN

可空型別…

  • 是泛型型別
  • 是 System.Nullable 結構的例項。
  • 只能在值型別上宣告。
  • 使用 System.Nullable<type> 或簡寫 type? 宣告 - 這兩種方式可以互換。
System.Nullable<int> MyNullableInt;  // the long version 
int? MyNullableInt;                  // the short version
  • 接受底層型別的正常值範圍,以及 null
bool? MyBoolNullable;  // valid values: true || false || null

小心使用 可空 布林值!在 if、for、while 或邏輯評估語句中,可空布林值將 null 值等同於 false - 它不會丟擲錯誤。

方法:T GetValueOrDefault() & T GetValueOrDefault(T defaultValue)
返回儲存的值或預設值(如果儲存的值設定為 null)。

屬性:HasValue & Value
可空型別有兩個只讀屬性:HasValueValue

HasValue 是一個布林屬性,如果 Value != null,則返回 true。它提供了一種在使用型別之前檢查型別是否為非 null 值的方法,以防止出現錯誤

 int? MyInt = null;
 int MyOtherInt;
 MyOtherInt = MyInt.Value + 1;    // Error! You can't add null + 1!!
 if (MyInt.HasValue) MyOtherInt = MyInt.Value + 1; // This is a better way.

返回您的型別的值,null 或其他。

int? MyInt = 27;
if (MyInt.HasValue) return MyInt.Value;  // returns 27.
MyInt = null;
return MyInt; // returns null.

包裝/解包

包裝是將非空型別N 中的值m 透過表示式new N?(m)打包到可空型別N?中的過程。

解包評估可空型別N?的例項m 作為型別N 或 NULL 的過程,並透過“值”屬性執行(例如m.Value)。

注意:解包空例項會生成異常System.InvalidOperationException

?? 運算子(也稱為空合併運算子)

雖然不是專門用於可空型別,但??運算子在您想要使用預設值而不是null值時非常有用。??運算子返回語句的左運算元(如果非空),否則返回右運算元。

int? MyInt = null;
return MyInt ?? 27;  // returns 27, since MyInt is null

有關更多資訊,請參閱R. Aaron Zupancic 關於 ?? 運算子的部落格文章

構建值型別

構建值型別必須非常簡單。以下示例定義了一個自定義“點”結構,它只有 2 個雙精度成員。有關將值型別隱式轉換為引用型別的討論,請參閱裝箱和拆箱

C# 程式碼示例

構建和使用自定義值型別(結構)

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace ValueTypeLab01
   {
       class Program
       {
           static void Main(string[] args)
           {
               MyPoint p;
               p.x = 3.2;
               p.y = 14.1;
               Console.WriteLine("Distance from origin: " + Program.Distance(p));
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // method where MyPoint is passed by value
           public static double Distance(MyPoint p)
           {
               return Math.Sqrt(p.x * p.x + p.y * p.y);
           }
       }
       // MyPoint is a struct (custom value type) representing a point
       public struct MyPoint
       {
           public double x;
           public double y;
       }
   }

使用使用者定義的值型別

以上示例可以在這裡使用。請注意,p 變數不必用new運算子初始化。

使用列舉

以下示例展示了 System 列舉 DayOfWeek 的簡單用法。該程式碼比測試表示一天的整數值要簡單得多。請注意,對列舉變數使用 ToString() 將給出值的字串表示(例如“星期一”而不是“1”)。

可以使用反射列出可能的值。有關詳細資訊,請參閱該部分。

有關 Enum 類討論,請參閱MSDN

有一種特殊型別的列舉,稱為標誌列舉。考試目標沒有專門提到它。如果您有興趣,請參閱MSDN

C# 示例

列舉的簡單使用

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace EnumLab01
   {
       class Program
       {
           static void Main(string[] args)
           {
               DayOfWeek day = DayOfWeek.Friday;
               if (day == DayOfWeek.Friday)
               {
                   Console.WriteLine("Day: {0}", day);
               }
               DayOfWeek day2 = DayOfWeek.Monday;
               if (day2 < day)
               {
                   Console.WriteLine("Smaller than Friday");
               }
               switch (day)
               {
                   case DayOfWeek.Monday:
                       Console.WriteLine("Monday processing");
                       break;
                   default:
                       Console.WriteLine("Default processing");
                       break;
               }
               int i = (int)DayOfWeek.Sunday;
               Console.WriteLine("Int value of day: {0}", i);
               // Finishing
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
   }

構建列舉

構建自定義列舉非常簡單,如下例所示。

C# 示例

宣告簡單列舉

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace EnumLab02
   {
       class Program
       {
           public enum MyColor
           {
               None = 0,
               Red,
               Green,
               Blue
           }
           static void Main(string[] args)
           {
               MyColor col = MyColor.Green;
               Console.WriteLine("Color: {0}", col);
               // Finishing
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
   }

使用引用型別

引用型別更常被稱為物件介面委託都是引用型別,以及內建的引用型別System.ObjectSystem.String。引用型別儲存在託管堆記憶體中。

與值型別不同,引用型別可以被賦值為null

複製引用型別會複製指向該物件的引用,而不是該物件的副本本身。這有時會讓人覺得很奇怪,因為更改引用副本也會更改原始引用。

值型別儲存它被賦值的值,簡單明瞭——但引用型別儲存指向記憶體中位置的指標(在堆上)。將堆視為一堆儲物櫃,而引用型別持有儲物櫃編號(在這個比喻中沒有鎖)。複製引用型別就像給某人一份儲物櫃編號的副本,而不是其內容的副本。指向同一記憶體的兩個引用型別就像兩個人共用同一個儲物櫃——兩個人都可以修改其內容

C# 程式碼示例

使用引用型別的示例

public class Dog
{
  private string breed;
  public string Breed { get {return breed;} set {breed = value;} }
  
  private int age;
  public int Age { get {return age;} set {age = value;} }
  
  public override string ToString()
  {
    return String.Format("is a {0} that is {1} years old.", Breed, Age);
  }
  
  public Dog(string dogBreed, int dogAge)
  {
    this.breed = dogBreed;
    this.age = dogAge;
  }
}

public class Example()
{
   public static void Main()
   {
     Dog myDog = new Dog("Labrador", 1);    // myDog points to a position in memory.
     Dog yourDog = new Dog("Doberman", 3);  // yourDog points to a different position in memory.

     yourDog = myDog; // both now point to the same position in memory, 
                    // where a Dog type has values of "Labrador" and 1
   
     yourDog.Breed = "Mutt";
     myDog.Age = 13; 

     Console.WriteLine("Your dog {0}\nMy dog {1}", yourDog.ToString(), myDog.ToString());
   }
}

由於 yourDog 變數和 myDog 變數都指向同一個記憶體儲存,因此其輸出將是

Your dog is a Mutt that is 13 years old.
My dog is a Mutt that is 13 years old.

作為操縱引用型別的練習,您可能想要使用String 和 StringBuilder類。我們將這些與文字操作部分放在一起,但操縱字串幾乎所有程式的基本操作。

使用和構建陣列

請參閱MSDN以獲取參考資訊。

使用類

構建自定義類

使用介面

構建自定義介面

使用特性

使用泛型型別

System 泛型型別的四大類別的使用將在本書的其它部分中進行演示。

  • 可空型別已在上面討論過。
  • 之後將有一整節內容介紹泛型集合。
  • 將在事件/委託部分討論泛型事件處理程式。
  • 泛型委託也將討論在事件/委託部分以及泛型集合部分(比較器類)。

如果您在 Visual Studio 中複製下一個非常簡單的示例並嘗試向列表中新增除 int 以外的任何內容,程式將無法編譯。這展示了泛型的強型別能力。

泛型的簡單使用(C#)

非常簡單的泛型使用

   using System;
   using System.Collections.Generic;
   namespace GenericsLab01
   {
       class Program
       {
           static void Main(string[] args)
           {
               List<int> myIntList = new List<int>();
               myIntList.Add(32);
               myIntList.Add(10); // Try to add something other than an int 
                                  // ex. myIntList.Add(12.5);
               foreach (int i in myIntList)
               {
                   Console.WriteLine("Item: " + i.ToString());
               }
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
   }

您可以使用 List<string> 而不是 List<int>,您將以同樣的價格獲得字串列表(您正在使用相同的 List(T) 類)。

構建泛型

文章中展示了自定義泛型集合的程式設計,該文章在主題討論中提到。

這裡我們有一個泛型函式的示例。我們使用交換兩個引用的簡單問題。雖然非常簡單,但我們仍然看到了泛型的基本優勢。

  • 我們不必為每種型別重新編寫一個交換函式。
  • 泛化不會讓我們失去強型別(嘗試交換 int 和字串,它不會編譯)。
簡單的自定義泛型函式(C#)

簡單的自定義泛型函式

   using System;
   using System.Collections.Generic;
   using System.Text;
   namespace GenericsLab03
   {
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Swap strings
               string str1 = "First string";
               string str2 = "Second string";
               pgm.swap<string>(ref str1, ref str2);
               Console.WriteLine(str1);
               Console.WriteLine(str2);
               // Swap integers
               int int1 = 1;
               int int2 = 2;
               pgm.swap<int>(ref int1, ref int2);
               Console.WriteLine(int1);
               Console.WriteLine(int2);
               // Finish with wait
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // Swapping references
           void swap<T>(ref T r1,ref T r2)
           {
               T r3 = r1;
               r1 = r2;
               r2 = r3;
           }
       }
   }


下一步是展示一個示例,包括泛型介面、實現該泛型介面的泛型類以及從該泛型類派生的類。該示例還使用介面和派生約束。

這是另一個簡單的涉及員工和供應商的問題,它們除了可以向“付款處理程式”請求付款外,別無共同之處(請參閱訪問者模式)。

問題是,如果您需要對特定型別的付款進行特定處理(僅針對員工),那麼應該將邏輯放在哪裡。有無數種方法可以解決這個問題,但使用泛型使得以下示例變得乾淨、明確且強型別。

另一個好處是,它與容器或集合無關,您將在其中找到幾乎所有泛型示例。

請注意,EmployeeCheckPayment<T> 類派生自 CheckPayment<T>,對型別引數 T 施加了更強的約束(必須是員工,而不僅僅是實現 IPaymentInfo)。這讓我們有機會在 RequestPayment 方法中訪問所有付款邏輯(來自基類)以及所有員工公共介面(透過 sender 方法引數),而且無需進行任何強制轉換。

自定義泛型介面和類(C#)

自定義泛型介面和類

   using System;
   using System.Collections.Generic;
   using System.Text;
   namespace GennericLab04
   {
       class Program
       {
           static void Main(string[] args)
           {
               // Pay supplier invoice
               CheckPayment<Supplier> checkS = new CheckPayment<Supplier>();
               Supplier sup = new Supplier("Micro", "Paris", checkS);
               sup.InvoicePayment();
               // Produce employee paycheck
               CheckPayment<Employee> checkE = new EmployeeCheckPayment<Employee>();
               Employee emp = new Employee("Jacques", "Montreal", "bigboss", checkE);
               emp.PayTime();
               // Wait to finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
       // Anything that can receive a payment must implement IPaymentInfo
       public interface IPaymentInfo
       {
           string Name { get;}
           string Address { get;}
       }
       // All payment handlers must implement IPaymentHandler
       public interface IPaymentHandler<T> where T:IPaymentInfo 
       {
           void RequestPayment(T sender, double amount);
       }
       // Suppliers can receive payments thru their payment handler (which is given by an  object factory)
       public class Supplier : IPaymentInfo
       {
           string _name;
           string _address;
           IPaymentHandler<Supplier> _handler;
           public Supplier(string name, string address, IPaymentHandler<Supplier> handler)
           {
               _name = name;
               _address = address;
               _handler = handler;
           }
           public string Name { get { return _name; } }
           public string Address { get { return _address; } }
           public void InvoicePayment()
           {
               _handler.RequestPayment(this, 4321.45);
           }
       }
       // Employees can also receive payments thru their payment handler (which is given by an  object factory)
       // even if they are totally distinct from Suppliers
       public class Employee : IPaymentInfo
       {
           string _name;
           string _address;
           string _boss;
           IPaymentHandler<Employee> _handler;
           public Employee(string name, string address, string boss, IPaymentHandler<Employee> handler)
           {
               _name = name;
               _address = address;
               _boss = boss;
               _handler = handler;
           }
           public string Name { get { return _name; } }
           public string Address { get { return _address; } }
           public string Boss { get { return _boss; } }
           public void PayTime()
           {
               _handler.RequestPayment(this, 1234.50);
           }
       }
       // Basic payment handler
       public class CheckPayment<T>  : IPaymentHandler<T> where T:IPaymentInfo
       {
           public virtual void RequestPayment (T sender, double amount) 
           {
               Console.WriteLine(sender.Name);
           }
       }
       // Payment Handler for employees with supplementary logic
       public class EmployeeCheckPayment<T> : CheckPayment<T> where T:Employee
       {
           public override void RequestPayment(T sender, double amount)
           {
               Console.WriteLine("Get authorization from boss before paying, boss is: " + sender.Boss);
	            base.RequestPayment(sender, amount);
           }
       }
   }

異常類

一些指向 MSDN 的連結

  • 異常和異常處理 - MSDN
  • 處理和丟擲異常 - MSDN
  • 異常層次結構 - MSDN
  • 異常類和屬性 - MSDN

裝箱和拆箱

請參閱MSDN

所有型別直接或間接地從 System.Object 派生(包括值型別,透過 System.ValueType)。這允許對“任何”物件的引用非常方便的概念,但會帶來一些技術問題,因為值型別沒有被“引用”。裝箱和拆箱隨之而來。

裝箱和拆箱使值型別能夠像物件一樣對待。裝箱值型別將它打包到 Object 引用型別的例項中。這允許值型別儲存在垃圾回收堆上。拆箱從物件中提取值型別。在此示例中,整型變數 i 被裝箱並賦值給物件 o

int i = 123;
object o = (object) i;  // boxing

另請注意,沒有必要顯式地將整數強制轉換為物件(如上面的示例所示)以使整數被裝箱。呼叫它的任何方法也會導致它在堆上被裝箱(因為只有裝箱形式的物件具有指向虛擬方法表的指標)。

int i=123;
String s=i.toString(); //This call will cause boxing

還有一種第三種方法可以裝箱值型別。這發生在您將值型別作為引數傳遞給期望物件的函式時。假設有一個函式原型為

void aFunction(object value)

現在假設從程式的另一個部分,您像這樣呼叫該函式

int i=123;
aFunction(i); //i is automatically boxed

此呼叫將自動將整數強制轉換為物件,從而導致裝箱。

然後可以將物件 o 拆箱並賦值給整型變數 i

o = 123;
i = (int) o;  // unboxing

裝箱和拆箱的效能

相對於簡單的賦值,裝箱和拆箱是計算量很大的過程。當裝箱值型別時,必須分配和構造一個全新的物件。在較小程度上,拆箱所需的強制轉換在計算上也是昂貴的。

TypeForwardedToAttribute 類

請參閱MSDN

有關 CLR 中 TypeForwardToAttribute 的討論,請參閱MSDN
其他可能的連結:Marcus 的部落格NotGartner


使用集合

考試目標:使用集合管理 .NET Framework 應用程式中一組關聯資料。

(參考 System.Collections 名稱空間 - MSDN)

ArrayList 類

參見 MSDN

ArrayList 類用於陣列,其大小將根據需要動態增加。ArrayList 不一定排序。

using System;
using System.Collections;
public class Demo {
    public static void Main() {
        ArrayList myArrayList = new ArrayList();
        myArrayList.Add("Testing");
        myArrayList.Add("1...2...3");
    }
}
集合介面
ICollection 介面和 IList 介面
ICollection 介面 - MSDN
ICollection 介面是 System.Collections 名稱空間中類的基本介面。
ICollection 介面擴充套件 IEnumerable; IDictionaryIList 是更專門的介面,它們擴充套件了 ICollectionIDictionary 實現是鍵值對的集合,如 Hashtable 類。IList 實現是值的集合,其成員可以透過索引訪問,如 ArrayList 類。
一些限制對元素訪問的集合,例如 Queue 類和 Stack 類,直接實現 ICollection 介面。
如果 IDictionary 介面和 IList 介面都不滿足所需集合的要求,則從 ICollection 介面派生新的集合類,以獲得更大的靈活性。
以下表格列出了 ICollection 型別公開的成員。
公共屬性
  Count - Gets the number of elements contained in the ICollection.  
  IsSynchronized - Gets a value indicating whether access to the ICollection is synchronized (thread safe).  
  SyncRoot - Gets an object that can be used to synchronize access to the ICollection.  

公共方法
  CopyTo - Copies the elements of the ICollection to an Array, starting at a particular Array index.   
IList 介面 - MSDN
IComparer 介面、IEqualityComparer 介面和 IKeyComparer 介面
IComparer 介面 - MSDN
IEqualityComparer 介面 - MSDN
IKeyComparer 介面 - IKeyComparer 在 .Net 2.0 中不存在
IDictionary 介面和 IDictionaryEnumerator 介面
IDictionary 介面 - MSDN
IDictionaryEnumerator 介面 - MSDN
IEnumerable 介面和 IEnumerator 介面 - MSDNMSDN
C# 程式碼示例

IEnumerator 示例

public class Person
{
   public Person(string fName, string lName)
   {
       this.firstName = fName;
       this.lastName = lName;
   }
   public string firstName;
   public string lastName;
}
public class PeopleEnum : IEnumerator
{
   public Person[] _people;
   //Enumerators are positioned before the first element
   //until the first MoveNext() call.
   int position = -1;
   public PeopleEnum(Person[] list)
   {
       _people = list;
   }
   public bool MoveNext()
   {
       position++;
       return (position < _people.Length);
   }
   public void Reset()
   {
       position = -1;
   }
   public object Current
   {
       get
       {
           try
           {
               return _people[position];
           }
           catch (IndexOutOfRangeException)
           {
               throw new InvalidOperationException();
           }
       }
   }
}
public class People : IEnumerable
{
   private Person[] _people;
   public People(Person[] pArray)
   {
       _people = new Person[pArray.Length];
       for (int i = 0; i < pArray.Length; i++)
       {
           _people[i] = pArray[i];
       }
   }
   public IEnumerator GetEnumerator()
   {
       return new PeopleEnum(_people);
   }
}

寫一個處理程式來練習上述程式碼。

protected void lnkEnumerator_Click(object sender, EventArgs e)
   {
       Person[] peopleArray = new Person[] {
           new Person("Irfan", "Akhtar"),
           new Person("Hammad", "Anwar"),
           new Person("Majid", "Aalim")     };
       PeopleEnum Prson = new PeopleEnum(peopleArray);
  • 使用 IEnumerator 的一種方法。
 while (Prson.MoveNext () )
 {
       Person P = (Person)Prson.Current;
       Response.Write("First Name : " + P.firstName + ", Last Name : " + P.lastName);
 }


  • 使用 IEnumerable 的一種方法。
People peopleList = new People(peopleArray);
foreach (Person p in peopleList)
    Response.Write("First Name : " + p.firstName + ", Last Name : " + p.lastName);
IHashCodeProvider 介面 - MSDN - 該介面現在已過時(從 .NET 2.0 開始)

迭代器

參見 MSDN

迭代器實際上是 IEnumerable 介面的輕量級版本。它主要與 foreach 語句一起使用。
通常,您將實現 IEnumerable 介面的 GetEnumerator 方法。
public class Colors : System.Collections.IEnumerable
{
    string[] colors = { "Red", "Green", "Blue" };
    public System.Collections.IEnumerator GetEnumerator()
    {
        for (int i = 0; i < colors.Length; i++)
        {
            yield return colors[i];
        }
    }
}
這使得可以使用標準 foreach 語句訪問該類。類不限於僅實現單個迭代器。可以提供多個迭代器,例如,可以對列表進行升序和降序迭代。要呼叫命名迭代器,請使用以下語法
foreach (int i in myList.NamedIterator())
{
    System.Console.WriteLine(i);
}
yield 語句標記迭代器執行將在後續迭代中恢復的點。這可用於提供多個 yield 語句
public System.Collections.IEnumerator GetEnumerator()
{
    yield return "Statement returned on iteration 1";
    yield return "Statement returned on iteration 2";
}
要在程式設計方式結束迭代,請使用
yield break;
語句。

Hashtable 類 - MSDN

用於表示鍵值對的集合。

CollectionBase 類和 ReadOnlyCollectionBase 類

CollectionBase 類 - MSDN
ReadOnlyCollectionBase 類 -MSDN

DictionaryBase 類和 DictionaryEntry 類

DictionaryBase 類 - MSDN
DictionaryEntry 結構 - MSDN

Comparer 類 - MSDN

Queue 類 - MSDN

SortedList 類 - MSDN

BitArray 類 - MSDN

Stack 類 - MSDN

<noinclide> </noinclude>


使用泛型集合

考試目標:透過使用泛型集合來提高 .NET Framework 應用程式中的型別安全性和應用程式效能。

(參考 System.Collections.Generic 名稱空間 MSDN )

Collection.Generic 介面

泛型 IComparable 介面 - MSDN
請注意,IComparable<T> 是 System 名稱空間的成員。
當您建立一個類,並且希望該類與支援排序的泛型型別(例如 SortedList<T> 或 List<T>.Sort())一起使用,而不必指定比較器物件時,您將使用此介面。IComparable<T> 的唯一方法是 CompareTo<T>(T other)。MSDN 上有一個示例。
以下示例對自定義的 Point 類實現 IComparable<T>。該示例使用 List<T> 而不是 SortedList<T> 或 OrderedDictionnary<T>,因為比較是基於點到原點的距離進行的,這可能會導致許多點具有相同的距離。
簡單使用 IComparable<T>(C#)

簡單使用 IComparable<T>(C#)

   using System;
   using System.Collections.Generic;
   using System.Text;
   namespace GenericsLab05
   {
       class Program
       {
           static void Main(string[] args)
           {
               List<Point> lst = new List<Point>();
               lst.Add(new Point(-2, -2));
               lst.Add(new Point(1, 1));
               lst.Add(new Point(2, 2));
               // Sort uses IComparable of Point
               lst.Sort();
               foreach (Point pt in lst)
               {
                   Console.WriteLine(pt.ToString());
               }
               // Wait to finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
       // This is out custom version of a point
       public struct Point : IComparable<Point>
       {
           public double x;
           public double y;
           public Point(double px, double py)
           {
               x = px;
               y = py;
           }
           // Comparaison done based on distance from origin
           public int CompareTo(Point other)
           {
               return Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2)).CompareTo
                   (Math.Sqrt(Math.Pow(other.x, 2) + Math.Pow(other.y, 2)));
           }
           public override string ToString()
           {
               return "(" + x.ToString() + "," + y.ToString() + ")";
           }
       }
   }
泛型 ICollection 介面和泛型 IList 介面
泛型 ICollection 介面 - MSDN
泛型 IList 介面 - MSDN
泛型 IComparer 介面和泛型 IEqualityComparer 介面
泛型 IComparer 介面 - MSDN
泛型 IEqualityComparer 介面 - MSDN
泛型 IDictionary 介面 - MSDN
泛型 IEnumerable 介面和泛型 IEnumerator 介面
泛型 IEnumerable 介面 - MSDN
另請參見 ONDotnet
泛型 IEnumerator 介面 - MSDN
IHashCodeProvider 介面 - MSDN - 該介面現在已過時(從 .NET 2.0 開始)
泛型字典
泛型 Dictionary 類和泛型 Dictionary.Enumerator 結構
泛型 Dictionary 類 - MSDN
泛型 Dictionary.Enumerator 結構 - MSDN
泛型 Dictionary.KeyCollection 類和 Dictionary.KeyCollection.Enumerator 結構
泛型 Dictionary.KeyCollection 類 - MSDN
Dictionary.KeyCollection.Enumerator 結構 - MSDN
泛型 Dictionary.ValueCollection 類和 Dictionary.ValueCollection.Enumerator 結構
泛型 Dictionary.ValueCollection 類 - MSDN
Dictionary.ValueCollection.Enumerator 結構 - MSDN

泛型 Comparer 類和泛型 EqualityComparer 類

泛型 Comparer 類 - MSDN
Comparer<T> 類充當基類,可輕鬆實現 IComparer<T> 介面。
該示例與 IComparable<T> 相同,只是現在將派生的 Comparer<T> 物件提供給 List<T>.Sort() 方法,而不是在 Point 上實現 IComparable<T> 介面。
這種方法有 2 個優點
  • 即使您無權訪問 Point 的原始碼,也可以使用它
  • 對於同一個 Point 類,您可以擁有多個派生自 Comparer 的類
自定義 Comparer<T>(C#)

自定義 Comparer<T>(C#)

   using System;
   using System.Collections.Generic;
   using System.Text;
   namespace GenericsLab06
   {
       class Program
       {
           static void Main(string[] args)
           {
               List<Point> lst = new List<Point>();
               lst.Add(new Point(-2, -2));
               lst.Add(new Point(1, 1));
               lst.Add(new Point(2, 2));
               // Sort uses IComparable of Point
               lst.Sort(new DistanceComparer());
               foreach (Point pt in lst)
               {
                   Console.WriteLine(pt.ToString());
               }
               // Wait to finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
       // This is out custom version of a point
       public struct Point 
       {
           public double x;
           public double y;
           public Point(double px, double py)
           {
               x = px;
               y = py;
           }
           public override string ToString()
           {
               return "(" + x.ToString() + "," + y.ToString() + ")";
           }
       }
       // Derive from base comparer class to implement IComparer<T>
       public class DistanceComparer : Comparer<Point>
       {
           public override int Compare(Point p1, Point p2)
           {
               return Math.Sqrt(Math.Pow(p1.x, 2) + Math.Pow(p1.y, 2)).CompareTo
                   (Math.Sqrt(Math.Pow(p2.x, 2) + Math.Pow(p2.y, 2)));
           }
       }
   }
泛型 EqualityComparer 類 - MSDN

泛型 KeyValuePair 結構

參見 MSDN

泛型 List 類、泛型 List.Enumerator 結構和泛型 SortedList 類

泛型 List 類 - MSDN
泛型列表類例項只需使用 List<T> 語法宣告,其中 T 是特定型別。
泛型 List.Enumerator 結構 - MSDN
泛型 SortedList 類 - MSDN

泛型 Queue 類和泛型 Queue.Enumerator 結構

泛型 Queue 類 - MSDN
泛型 Queue.Enumerator 結構 - MSDN

泛型 SortedDictionary 類

參見 MSDN
有關 SortedList 和 SortedDictionary 之間的差異,請參見 MSDN

泛型連結串列

泛型連結串列表示雙向連結串列,是一種通用連結串列。它支援列舉器,並實現 ICollection 介面,與 .NET Framework 中的其他類一致。
泛型 LinkedList 類 - MSDN
泛型 LinkedList.Enumerator 結構 - MSDN
泛型 LinkedListNode 類 - MSDN

泛型 Stack 類和泛型 Stack.Enumerator 結構

泛型 Stack 類 - MSDN
泛型 Stack.Enumerator 結構 - MSDN



使用專門的集合

考試目標:透過使用專門的集合來管理 .NET Framework 應用程式中的資料。

(參考 System.Collections.Specialized 名稱空間)

專門的字串類

StringCollection 類 - MSDN
StringDictionary 類 - MSDN
StringEnumerator 類 - MSDN
專門的字典類
HybridDictionary 類 - MSDN
IOrderedDictionary 介面和 OrderedDictionary 類
IOrderedDictionary 介面 - MSDN
OrderedDictionary 類 - MSDN
ListDictionary 類 - MSDN

命名集合

NameObjectCollectionBase 類 - MSDN
NameObjectCollectionBase.KeysCollection 類 - MSDN
NameValueCollection 類 - MSDN

CollectionsUtil 類

CollectionsUtil 類 - MSDN

BitVector32 結構和 BitVector32.Section 結構

BitVector32 結構 - MSDN
BitVector32.Section 結構 - MSDN

標準介面

考試目標:透過實現 .NET Framework 介面,使元件符合標準契約。

(參考 System 名稱空間)

IComparable 介面 - MSDN

IComparable 介面定義了一種比較方法,值型別或類實現該方法以建立型別特定的比較方法

IDisposable 介面 - MSDN

IDispose 介面可用於在自定義類中顯式釋放非託管資源。當不再需要物件時,物件的使用者可以呼叫此方法。
.Net 垃圾回收器在不再使用託管物件時釋放分配給它們的記憶體,但是,無法預測垃圾回收何時發生,而且它不知道非託管資源,例如視窗控制代碼或開啟的檔案和流。

IConvertible 介面 - MSDN

ICloneable 介面 - MSDN

INullableValue 介面 - MSDN

IEquatable 介面 - MSDN

IFormattable 介面 - MSDN



使用事件和委託

考試目標:使用事件和委託控制 .NET Framework 應用程式元件之間的互動。

(參考 System 名稱空間)

Delegate 類 - MSDN

委託儲存指向一個或多個函式的指標,並在需要時呼叫它們。
委託的一種常見用途是事件處理。引發事件的類不知道哪些物件或方法想要接收事件,因此在引發事件的物件和接收事件的物件之間需要一箇中介或指標機制。委託可以用作函式指標來實現這一點。
委託是一個類,但與普通類不同,它有一個簽名。在 .Net 框架中,您只需宣告委託,CLR 會處理類的實現。
   //delegate declaration
   public delegate void AlarmEventHandler(object sender,EventArgs e);
第一個完整示例只聲明瞭一個委託型別,然後聲明瞭該型別的變數,將函式分配給它並執行委託變數,這將執行函式。
C# 示例

簡單委託

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace DelegateLab01
   {
       // declare the delegate type
       public delegate int IntOperDel(int i);
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Assign the delegate
               IntOperDel deleg = pgm.Increment;
               // Executing the delegate
               int res = deleg(32);
               Console.WriteLine("First value: " + res.ToString());
               // Second assign
               deleg = pgm.Decrement;
               // Second execution
               res = deleg(32);
               Console.WriteLine("First value: " + res.ToString());
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // First function to be assigned to the delegate
           public int Increment(int n)
           {
               return n + 1;
           }
           // Second function to be assigned to the delegate
           public int Decrement(int n)
           {
               return n - 1;
           }
       }
   }
第二個委託示例實現了回撥函式的概念。使用委託作為引數呼叫函式。當它執行委託時,它不知道到底執行了什麼函式。它的部分行為委託給作為引數傳遞的函式(透過委託)。
C# 示例

回撥委託

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace DelegateLab02
   {
       // declare the delegate type
       public delegate int IntOperDel(int i);
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Assign the delegate
               IntOperDel deleg = pgm.Increment;
               // Calling a function that will execute de delegate
               // as part of its own logic
               pgm.ExecuteCallBack(deleg);
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // Function to be assigned to a delegate
           public int Increment(int n)
           {
               return n + 1;
           }
           // Function called with a delegate as parameter
           public void ExecuteCallBack(IntOperDel deleg)
           {
               int res = deleg(32);
               Console.WriteLine("Result from executing the callback: " + res.ToString());
           }
       }
   }
第三個委託示例使用委託成員來產生與事件相同的模式。請注意,在沒有分配至少一個函式的情況下執行委託成員會導致異常。另外,使用 += 運算子分配兩個函式將在執行委託時執行這兩個函式。如果委託具有返回值,則將返回最後執行函式的返回值。
C# 示例

委託成員

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace DelegateLab03
   {
       // declare the delegate type
       public delegate void IntOperDel(int i);
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Use += oper to assign functions to the delegate
               pgm.delMember += pgm.Increment;
               pgm.delMember += pgm.Decrement;
               // Calling some member function that will execute the delegate
               pgm.ExecuteSomething();
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // First function to be assigned to a delegate
           public void Increment(int n)
           {
               int res = n + 1;
               Console.WriteLine("Inside increment function: " + res.ToString());
           }
           // Second function to be assigned to a delegate
           public void Decrement(int n)
           {
               int res = n - 1;
               Console.WriteLine("Inside decrement function: " + res.ToString());
           }
           // Class member of the delegate type
           public IntOperDel delMember;
           // Function called to execute the delegate member (raise the "event")
           public void ExecuteSomething()
           {
               this.delMember(32);
           }
       }
   }
第四個示例是事件的基本示例。它與第三個示例完全相同,只是在成員宣告中添加了event關鍵字,並且在呼叫事件之前進行了測試,因為“空”事件會丟擲異常。此示例清楚地表明,事件不過是多播委託。
C# 示例

事件成員

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace DelegateLab04
   {
       // declare the delegate type
       public delegate void IntOperDel(int i);
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Use += oper to assign functions to the delegate
               pgm.delMember += pgm.Increment;
               pgm.delMember += pgm.Decrement;
               // Calling some member function that will execute the delegate
               pgm.ExecuteSomething();
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // First function to be assigned to a delegate
           public void Increment(int n)
           {
               int res = n + 1;
               Console.WriteLine("Inside increment function: " + res.ToString());
           }
           // Second function to be assigned to a delegate
           public void Decrement(int n)
           {
               int res = n - 1;
               Console.WriteLine("Inside decrement function: " + res.ToString());
           }
           // Class member event of the delegate type
           public event IntOperDel delMember;
           // Function called to execute the delegate member (raise the "event")
           public void ExecuteSomething()
           {
               if (this.delMember != null)
                   this.delMember(32);
           }
       }
   }
執行與事件關聯的函式稱為引發事件。
與事件關聯的函式稱為事件處理程式

EventArgs 類 - MSDN

按照約定,事件使用一個返回 void 並接受兩個引數的委託
  • 一個 System.Object 型別的物件,其中包含對引發事件的物件的引用。
  • 來自從 EventArgs 派生的類的物件,其中包含從引發事件的物件傳遞到事件處理程式的資料。
EventArgs 類本身不包含任何資料。
因此,沒有資料的事件將使用以下形式的委託
public delegate void DelegateTypeName (object sender, EventArgs e)
此簡單的事件示例與上一個示例相同,只是使用了 IntEventArgs 類來將 int 引數傳遞給事件處理程式,並且所有引數都更改為遵循事件的呼叫約定。
C# 示例

具有遵循呼叫約定的委託的事件

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace EventLab01
   {
       // the class containing the event data passed to the event handler
       public class IntEventData : EventArgs
       {
           public int IntParm = 0;
           // constructor
           public IntEventData(int i)
           {
               IntParm = i;
           }
       }
       // the delegate type
       public delegate void IntOperDel(object sender, IntEventData e);
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Use += oper to assign functions to the delegate
               pgm.delMember += pgm.Increment;
               pgm.delMember += pgm.Decrement;
               // Calling some member function that will execute the delegate
               pgm.ExecuteSomething();
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // First function to be assigned to a delegate
           public void Increment(object sender, IntEventData e)
           {
               int res = e.IntParm + 1;
               Console.WriteLine("Inside increment function: " + res.ToString());
           }
           // Second function to be assigned to a delegate
           public void Decrement(object sender, IntEventData e)
           {
               int res = e.IntParm - 1;
               Console.WriteLine("Inside decrement function: " + res.ToString());
           }
           // Class member event of the delegate type
           public event IntOperDel delMember;
           // Function called to execute the delegate member (raise the "event")
           public void ExecuteSomething()
           {
               if (this.delMember != null)
                   this.delMember(this, new IntEventData(32));
           }
       }
   }

EventHandler 委託 - MSDNMSDN

System 名稱空間中定義了兩個特殊的委託,以幫助您進行事件宣告。
第一個是 EventHandler 委託。它不傳遞任何資料。不傳遞資料的事件可以宣告為
public event EventHandler EventName
而無需宣告自定義委託。
這是宣告不傳遞資料的事件的常用方法。
C# 示例

具有 EventHandler 委託的事件

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace EventLab03
   {
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Use += oper to assign functions to the delegate
               pgm.delMember += pgm.Increment;
               pgm.delMember += pgm.Decrement;
               // Calling some member function that will execute the delegate
               pgm.ExecuteSomething();
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // First function to be assigned to the event
           public void Increment(object sender, EventArgs e)
           {
               int res = 32 + 1;
               Console.WriteLine("Inside increment function: " + res.ToString());
           }
           // Second function to be assigned to the event
           public void Decrement(object sender, EventArgs e)
           {
               int res = 32 - 1;
               Console.WriteLine("Inside decrement function: " + res.ToString());
           }
           // Class member event of the delegate type
           public event EventHandler delMember;
           // Function called to execute the delegate member (raise the "event")
           public void ExecuteSomething()
           {
               if (this.delMember != null)
                   this.delMember(this, null);
           }
       }
   }
第二個是 EventHandler<T> 泛型委託,其中 T 是從 EventArgs 派生的型別
public event EventHandler<T> EventName
同樣不需要宣告自定義委託型別。
這是宣告向事件處理程式傳遞資料的事件的常用方法。
請注意,在這種情況下,您仍然需要宣告從 EventArgs 派生的型別 T。
C# 示例

利用 EventHandler<T> 的事件

   using System;
   using System.Collections.Generic;
   using System.Text;
   //
   namespace EventLab02
   {
       // the class containing the event data passed to the event handler
       public class IntEventData : EventArgs
       {
           public int IntParm = 0;
           // constructor
           public IntEventData(int i)
           {
               IntParm = i;
           }
       }
       //
       class Program
       {
           static void Main(string[] args)
           {
               Program pgm = new Program();
               // Use += oper to assign functions to the delegate
               pgm.delMember += pgm.Increment;
               pgm.delMember += pgm.Decrement;
               // Calling some member function that will execute the delegate
               pgm.ExecuteSomething();
               // Wait for finish
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
           // First function to be assigned to a delegate
           public void Increment(object sender, IntEventData e)
           {
               int res = e.IntParm + 1;
               Console.WriteLine("Inside increment function: " + res.ToString());
           }
           // Second function to be assigned to a delegate
           public void Decrement(object sender, IntEventData e)
           {
               int res = e.IntParm - 1;
               Console.WriteLine("Inside decrement function: " + res.ToString());
           }
           // Class member event of the delegate type
           public event EventHandler<IntEventData> delMember;
           // Function called to execute the delegate member (raise the "event")
           public void ExecuteSomething()
           {
               if (this.delMember != null)
                   this.delMember(this, new IntEventData(32));
           }
       }
   }
關於事件和委託的基本示例到此結束。



服務、執行緒和應用程式域

考試目標:在 .NET Framework 應用程式中實現服務程序、執行緒和應用程式域

主題

服務

此處“服務”一詞指的是 Windows 服務。Windows 服務的基本定義是長時間執行的程序,不需要使用者介面。為什麼需要一個不需要使用者介面的長時間執行的程序?主要有兩個原因

  • 執行不需要使用者干預的維護任務。例如,備份軟體會定期檢查備份計劃並在需要時執行不同的備份任務。這不需要使用者介面。
  • 響應來自其他程序或來自作業系統的請求。例如,IIS(處理 Web 請求的 Windows 元件)這樣的 http 伺服器將接收來自客戶端瀏覽器的 http 請求,並向這些相同的瀏覽器生成響應(html 頁面)。資料庫程序是另一個很好的例子。同樣,http 伺服器不需要使用者介面,因為與客戶端的介面由客戶端瀏覽器元件管理。

關於服務的考試目標非常基礎,涉及到您在處理服務時遇到的第一個問題

  • 由於服務沒有使用者介面,那麼誰會啟動和停止它呢?答案是作業系統直接執行服務,但您必須將您的服務“註冊”以讓系統知道它在哪裡以及如何處理它(這就是安裝過程)
  • 具有使用者介面的程序本質上是在等待來自使用者的事件。在沒有訊息泵的情況下,服務是如何“工作”的(訊息泵是在典型的線上應用程式中獲取使用者輸入的技術)?
  • 如果服務執行使用者介面功能,它將“掛起”,等待不存在的使用者。您如何避免這種情況?

更重要、更復雜的架構問題不在本考試範圍內,將在企業開發考試中討論。

多執行緒


Clipboard

待辦事項
對 .NET 中的多執行緒支援的描述


應用程式域


Clipboard

待辦事項
CLI 中應用程式域的簡短描述


類、介面和工具

實現、安裝和控制服務

考試目標:實現、安裝和控制服務

(參考 System.ServiceProcess 名稱空間)

從 ServiceBase 類繼承 - MSDN

服務是長時間執行的可執行檔案。它不提供使用者介面,也不需要任何使用者登入到計算機。服務以 System 身份執行,但可以選擇以其他使用者帳戶執行。ServiceBase 類是服務的基類。在建立新服務時,必須從它派生。
幾乎所有服務都將覆蓋 ServiceBase 的 OnStart 和 OnStop 方法。

ServiceController 類和 ServiceControllerPermission 類

ServiceController 類 - MSDN
ServiceControllerPermission 類 - MSDN

ServiceInstaller 和 ServiceProcessInstaller 類

ServiceInstaller - MSDN
ServiceProcessInstaller 類 - MSDN

ServiceChangeDescription 結構和 ServiceChangeReason 列舉

SessionChangeDescription 結構 - MSDN
SessionChangeReason 列舉 - MSDN

開發多執行緒應用程式

考試目標:開發多執行緒 .NET Framework 應用程式

(參考 System.Threading 名稱空間)

Thread 類 - MSDN

ThreadPool 類 - MSDN

ThreadStart 委託、ParameterizedThreadStart 委託和 SynchronizationContext 類

ThreadStart 委託 - MSDN
建立執行緒最簡單的方法是例項化 Thread 類。Thread 建構函式接受一個委託引數。ThreadStart 委託指向包含你的邏輯的方法。例如
Thread t1 = new Thread (new ThreadStart(LengthyLogic));
public void LengthyLogic ()
{
  // Logic code
}
ParameterizedThreadStart 委託 - MSDN
當你啟動執行緒時,有時你需要傳遞一些資料進行處理。.NET 2.0 提供了一個新的委託 ParameterizedThreadStart,它接受型別為 object 的引數。該類有一個新的過載函式 Thread.Start。它允許你指定要傳遞到執行緒的值。這種方法很簡單,但不是型別安全的。例如
Thread t1 = new Thread(new ParameterizedThreadStart(LengthyLogic));
// Use the overload of the Start method that has a parameter of type Object.
t1.Start(myData);
static void LengthyLogic(object data)
{
  // Logic code
}
SynchronizationContext 類 - MSDN
SynchronizationContext 類的 Code Project 示例 [1]

Timeout 類、Timer 類、TimerCallback 委託、WaitCallback 委託、WaitHandle 類和 WaitOrTimerCallback 委託

Timeout 類 - MSDN
Timer 類 - MSDN
TimerCallback 委託 - MSDN
WaitCallback 委託 - MSDN
WaitHandle 類 - MSDN
WaitOrTimerCallback 委託 - MSDN

ThreadExceptionEventArgs 類和 ThreadExceptionEventHanlder 類

ThreadExceptionEventArgs 類 - MSDN
ThreadExceptionEventHandler 類 - MSDN

ThreadState 列舉和 ThreadPriority 列舉

ThreadState 列舉 - MSDN
ThreadPriority 列舉 - MSDN

ReaderWriterLock 類 - MSDN

AutoResetEvent 類和 ManualResetEvent 類

AutoResetEvent 類 - MSDN
ManualResetEvent 類 - MSDN

IAsyncResult 介面和 ICancelableAsyncResult 介面

(參考 System 名稱空間)
IAsyncResult 介面 - MSDN
ICancelableAsyncResult 介面 - MSDN

EventWaitHandle 類、RegisterWaitHandle 類、SendOrPostCallback 委託和 IOCompletionCallback 委託

EventWaitHandle 類 - MSDN
RegisterWaitHandle 類 - MSDN
這是考試目標列表和培訓套件中的一個錯字。術語RegisterWaitForSingleObject應該改為搜尋(參見 KB
SendOrPostCallback 委託 - MSDN
IOCompletionCallback 委託 - MSDN

Interlocked 類、NativeOverlapped 結構和 Overlapped 類

Interlocked 類 - MSDN
NativeOverlapped 結構 - MSDN
Overlapped 類 - MSDN

ExecutionContext 類、HostExecutionContext 類、HostExecutionContext 管理器和 ContextCallback 委託

ExecutionContext 類 - MSDN
HostExecutionContext 類 - MSDN
HostExecutionContext 管理器 - MSDN
實際上這裡指的是 HostExecutionContextManager 類
ContextCallback 委託 - MSDN

LockCookie 結構、Monitor 類、Mutex 類和 Semaphore 類 MSDN]

LockCookie 結構 - MSDN
Monitor 類 - MSDN
Mutex 類 - MSDN
Semaphore 類 - MSDN
Lock 與 Monitor 與 Mutex - MSDN

使用應用程式域

考試目標:透過使用應用程式域,在 .NET Framework 應用程式中為公共語言執行時建立隔離單元

(參考 System 名稱空間)

建立應用程式域

參見 MSDN

應用程式域是將程序劃分為多個部分。在不同應用程式域中執行的應用程式與它們在不同程序中執行一樣隔離。因此它們無法訪問另一個應用程式域中的記憶體。但是,如果執行的是本機程式碼,它可以獲得對整個程序的無限訪問許可權,包括其他應用程式域。

應用程式域更容易維護,速度也更快,因為在應用程式域之間進行通訊比在程序之間進行通訊更容易。應用程式域可以儲存多個程式集。

要建立應用程式域,你必須至少提供新應用程式域的名稱

 AppDomain ad = AppDomain.CreateDomain("Name");

使用AppDomain.CurrentDomain獲取呼叫執行緒正在使用的應用程式域。

將程式集載入到應用程式域中

參見 MSDN

可以使用AppDomain.ExecuteAssemblyByName按名稱執行程式集

 AppDomain ad = AppDomain.CreateDomain("Name");
 ad.ExecuteAssemblyByName("aname, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a9b8c7d6");

或者使用AppDomain.ExecuteAssembly提供程式集的路徑

 AppDomain ad = AppDomain.CreateDomain("Name");
 ad.ExecuteAssembly(@"c:\path\to\file.exe");
解除安裝應用程式域

參見 MSDN

無法從預設應用程式域中解除安裝程式集。但是,如果程式集載入到不同的應用程式域中,你可以解除安裝整個應用程式域,包括該應用程式域中的所有程式集。

要解除安裝應用程式域,請使用靜態AppDomain.Unload函式

 AppDomain ad = AppDomain.CreateDomain("Name");
 AppDomain.Unload(ad);  
配置應用程式域

參見 MSDN

修改應用程式域配置的最可能原因是限制某些許可權,以在攻擊者利用程式集中的漏洞時限制損害。

一個示例是在Internet Zone中執行程式集。Internet Zone 具有有限的許可權。為此,建立一個Zone Evidence,並在建立應用程式域時將其作為引數提供

 object [] myEvidenceTypes = {new Zone (SecurityZone.Internet)};
 Evidence myEvidence = new  Evidence(myEvidenceTypes, null);
 AppDomain ad = AppDomain.CreateDomain("Name", myEvidence); // Pass the Evidence when creating the App. Domain
 ad.ExecuteAssembly(@"c:\path\to\file.exe");
 

也可以在具有不同許可權的應用程式域中只執行一個程式集;

 object [] myEvidenceTypes = {new Zone (SecurityZone.Internet)};
 Evidence myEvidence = new  Evidence(myEvidenceTypes, null);
 AppDomain ad = AppDomain.CreateDomain("Name");
 ad.ExecuteAssembly(@"c:\path\to\file.exe", myEvidence); // Pass the Evidence in the ExecuteAssembly function

除了 Evidence,你還可以使用 AppDomainSetup 類來設定其他屬性。

 AppDomainSetup ads = new AppDomainSetup();
 ads.ApplicationBase = @"c:\Test";
 ads.DisallowCodeDownload = true;
 AppDomain ad = AppDomain.CreateDomain("Name", null, ads); // use null as second parameter for default Evidence
從應用程式域中檢索設定資訊

參見 MSDN

使用 AppDomain 的SetupInformation屬性從該應用程式域中讀取設定;

 AppDomainSetup ads = AppDomain.CurrentDomain.SetupInformation;
 Console.WriteLine(ads.ConfigurationFile);
 Console.WriteLine(ads.ApplicationName);


配置、診斷、管理和安裝

考試目標:將配置、診斷、管理和安裝功能嵌入到 .NET Framework 應用程式中

主題

配置管理

這裡以限制的方式使用配置管理。它指的是將應用程式適應到特定的執行環境或使用者。這通常透過使用配置檔案來完成,這些配置檔案指定應用程式的執行時引數。此類配置資訊的典型示例是用於定位和連線到應用程式資料庫的連線字串。考試目標都與應用程式與其配置檔案的實際互動有關。

配置檔案本身的管理或設計是一個龐大的主題,考試目標沒有涉及,因此在本學習指南中不會介紹。

.NET Framework 安裝程式

事件日誌

MSDN 的定義是“Windows 事件日誌允許你的應用程式和元件記錄有關重要事件的資訊。你可以使用這些記錄來稽核對系統的訪問、排查問題以及重新建立使用模式”。

有關一般討論,請參見 MSDN

有關 EventLog 類的詳細資訊以及使用它的注意事項,請參見 MSDN

效能監控

除錯和跟蹤

管理資訊和事件

Windows Management Instrumentation - MSDN



Clipboard

待辦事項
描述 WMI 在 .NET 中的整合


類、介面和工具

嵌入配置管理

考試目標:將配置管理功能嵌入到 .NET Framework 應用程式中。

(參考 System.Configuration 名稱空間)

Configuration 類和 ConfigurationManager 類

Configuration 類 - MSDN
ConfigurationManager 類 - MSDN

ConfigurationSettings 類、ConfigurationElement 類、ConfigurationElementCollection 類和 ConfigurationElementProperty 類

ConfigurationSettings 類 - MSDN
ConfigurationElement 類 - MSDN
ConfigurationElementCollection 類 - MSDN
ConfigurationElementProperty 類 - MSDN

實現 IConfigurationSectionHandler 介面 - MSDN

ConfigurationSection 類、ConfigurationSectionCollection 類、ConfigurationSectionGroup 類和 ConfigurationSectionGroupCollection 類

ConfigurationSection 類 - MSDN
ConfigurationSectionCollection 類 - MSDN
ConfigurationSectionGroup 類 - MSDN
ConfigurationSectionGroupCollection - MSDN

實現 ISettingsProviderService 介面 - MSDN

實現 IApplicationSettingsProvider 介面 - MSDN

ConfigurationValidationBase 類 - MSDN

在 MSDN 上沒有直接的結果 - 待檢查

實現 IConfigurationSystem 介面 - MSDN

建立自定義安裝程式並配置應用程式

考試目標:使用 System.Configuration.Install 名稱空間建立自定義 Microsoft Windows 安裝程式以用於 .NET Framework 元件,並使用配置檔案、環境變數和 .NET Framework 配置工具 (Mscorcfg.msc) 配置 .NET Framework 應用程式。

有關本節中討論的程式的“食譜”,請參閱 MSDN 和相應的 How-To 部分。

Installer 類 - MSDN

配置 .NET Framework 應用程式應使用的執行時版本 - MSDN

配置執行時應在何處搜尋程式集 - MSDN

配置程式集的位置以及要使用的程式集版本 - MSDNMSDN

指示執行時在搜尋程式集時使用 DEVPATH 環境變數 - MSDN

AssemblyInstaller 類 - MSDN

ComponentInstaller 類 - MSDN

使用 .NET Framework 配置工具 (Mscorcfg.msc) 配置 .NET Framework 應用程式 - MSDN

ManagedInstaller 類 - MSDN

InstallContext 類 - MSDN

InstallerCollection 類 - MSDN

實現 IManagedInstaller 介面 - MSDN

InstallEventHandler 委託 - MSDN

配置併發垃圾回收 - MSDN

使用配置檔案註冊遠端物件 - MSDN

管理事件日誌

考試目標:使用 System.Diagnostics 名稱空間管理事件日誌

EventLog 類 - MSDN

EventSourceCreationData 類 - MSDN

寫入事件日誌

MSDN

從事件日誌讀取

MSDN

建立新的事件日誌

透過建立第一個寫入該日誌的事件源來建立 EventLog。

執行此操作的兩種最簡單方法是

  • 使用 EventLog.CreateEventSource 方法
  • 建立 EventLog 例項,指定源,然後寫入日誌。實際建立發生在執行第一個寫入時。

請注意,即使在登錄檔中建立了表示源的物件,System.Diagnostics 名稱空間中也沒有“EventSource”類。

C# EventLog 建立示例

   using System;
   using System.Collections.Generic;
   using System.Text;
   using System.Diagnostics;
   namespace EventLogLab1
   {
       class Program
       {
           static void Main(string[] args)
           {
               try
               {
                   EventLog log1 = new EventLog("EvtLab2Log");
                   log1.Source = "EvtLab2Source";
                   // Actual creation happens next
                   log1.WriteEntry("Example message", EventLogEntryType.Information,
                       123, 1);
               }
               catch (Exception e)
               {
                   Console.WriteLine(e.Message);
               }
               Console.WriteLine("Press ENTER to finish");
               Console.ReadLine();
           }
       }
   }

建議的方法是使用 EventLogInstaller 類在應用程式安裝期間進行安裝,但這似乎沒有在培訓工具包中介紹(因此可能不在考試中)。有關參考目的,請參閱 MSDN

管理程序並監控效能

考試目標:使用 .NET Framework 2.0 的診斷功能管理系統程序並監控 .NET Framework 應用程式的效能。

(參考 System.Diagnostics 名稱空間)

獲取所有正在執行的程序的列表。

Process 類 - MSDN
有關 GetCurrentProcess()、GetProcessesByName()、GetProcesses() 和 GetProcessById() 的示例程式碼,請參閱 MSDN

檢索有關當前程序的資訊 - MSDN

獲取程序載入的所有模組的列表

Process.Modules 屬性返回一個強型別集合,其中包含代表 Process 當前載入的模組的 ProcessModule 物件。
有關 Process.Modules 屬性,請參閱 MSDN
有關 ProcessModule 類,請參閱 MSDN

PerformanceCounter 類、PerformanceCounterCategory 類和 CounterCreationData 類

PerformanceCounter 類 - MSDN
PerformanceCounterCategory - MSDN
CounterCreationData 類 - MSDN

使用和不使用命令列引數啟動程序

啟動程序概述
使用一個或多個過載的 Process.Start() 方法啟動程序。當將敏感資料(如密碼)傳遞給 Process.Start() 時,請使用接受 SecureString 作為引數型別的兩個過載 Start() 方法之一。
外部連結

StackTrace 類 - MSDN

StackFrame 類 - MSDN

除錯和跟蹤

考試目標:使用 System.Diagnostics 名稱空間除錯和跟蹤 .NET Framework 應用程式。

Debug 類和 Debugger 類

Debug 類 - MSDN
Debug 類的四個靜態寫入方法 - WriteWriteLineWriteIfWriteLineIf - 允許您將除錯訊息寫入以檢查程式流程並捕獲錯誤。在程式的釋出版本中,對這些方法的呼叫將被忽略(有關詳細資訊,請參閱下面的“ConditionalAttribute 屬性”)。
在 Visual Studio 中,Debug 的寫入方法的預設目標是輸出視窗。您可以使用 Debug 的 Listeners 屬性訪問關聯的 TraceListenerCollection。以下程式碼顯示瞭如何使用集合的 Remove 和 Add 方法來控制除錯訊息傳送到的位置。如果您添加了多個相同型別的偵聽器,則關聯的目標會多次接收文字。
C# 程式碼示例

Debug 類示例

   using System;
   using System.Diagnostics;
   using System.IO;
   using System.Reflection;
   class Program
   {
       static void Main(string[] args)
       {
           Debug.WriteLine("This is (by default) printed in the Output window.");
           //remove default listener
           Debug.Listeners.RemoveAt(0);
           //add a listener that can write to the Console window
           Debug.Listeners.Add(new ConsoleTraceListener());
           Debug.WriteLine("This is printed in the console window.");
           //add a default listener again
           Debug.Listeners.Add(new DefaultTraceListener());
           Debug.WriteLine("This is printed in both the Output and the Console window.");
           //remove all listeners
           Debug.Listeners.Clear();
           //add a listener that writes to a file
           Debug.Listeners.Add(new TextWriterTraceListener(File.Create("C:\\test.txt")));
           Debug.WriteLine("This is only printed to the newly created file.");
           //here we need to flush the output buffer
           Debug.Flush();
           //keep console window open in debug mode
           Console.ReadLine();
       }
   }
Debugger 類 - MSDN

Trace 類 - MSDN

CorrelationManager 類 - MSDN

TraceListener 類 - MSDN

TraceSource 類 - MSDN

TraceSwitch 類 - MSDN

XmlWriterTraceListener 類 - MSDN

DelimitedListTraceListener 類 - MSDN

EventlogTraceListener 類 - MSDN

偵錯程式屬性 - MSDN

DebuggerBrowsableAttribute 類 - MSDN
DebuggerDisplayAttribute 類 - MSDN
DebuggerHiddenAttribute 類 - MSDN
DebuggerNonUserCodeAttribute 類 - MSDN
DebuggerStepperBoundaryAttribute 類 - MSDN
DebuggerStepThroughAttribute 類 - MSDN
DebuggerTypeProxyAttribute 類 - MSDN
DebuggerVisualizerAttribute 類 - MSDN

嵌入管理資訊

考試目標: 將管理資訊和事件嵌入到 .NET Framework 應用程式中。

(參考 System.Management 名稱空間 - MSDN)

使用 ManagementObjectSearcher 類及其派生類檢索管理物件集合

System.Management 名稱空間中的類允許您使用 Windows 管理規範 (WMI) 來管理計算機系統。此名稱空間中最值得注意的類是 ManagementObjectSearcher,您可以使用它根據 WMI 查詢檢索管理物件。
可以透過這種方式檢索的資訊範圍從計算機機箱型別(筆記型電腦、桌上型電腦……)到處理器和硬碟詳細資訊,再到有關正在執行的服務和程序的資訊。請參閱 MSDN 上的 WMI 參考,瞭解所有 WMI 類及其屬性的概述。
在下面的示例中,定義了一個粗略且不乾淨的方法,用於列印給定 WMI 類中所有管理資訊物件的屬性。然後使用它來列印有關當前計算機系統的資訊。
C# 示例程式碼

WMI 基本示例

   class Program
   {
       static void Main(string[] args)
       {
           //Print all management info in the WMI class Win32_ComputerSystem
           PrintManagementInfo("Win32_ComputerSystem");
           //wait for user input to keep console window up in debug mode
           Console.ReadLine();
       }
       static void PrintManagementInfo(string WMIClassName)
       {
           ManagementObjectSearcher mos;
           //Get all managementobjects of the specified class
           mos = new ManagementObjectSearcher("SELECT * FROM " + WMIClassName);
           foreach (ManagementObject MOCollection in mos.Get())
           {
               foreach (PropertyData p in MOCollection.Properties)
               {
                   //Some properties are arrays,
                   //in which case the code below only prints
                   //the type of the array.
                   //Add a check with IsArray() and a loop
                   //to display the individual array values.
                   Console.WriteLine("{0}: {1}", p.Name, p.Value);
               }
           }
       }
   }
ManagementObjectSearcher 類 - MSDN
列舉計算機上的所有磁碟驅動器、網路介面卡和程序
以下程式碼使用 ManagementObjectSearcher 建構函式 的一個過載來列出系統上的所有物理和邏輯磁碟驅動器、網路介面卡和正在執行的程序。
C# 示例程式碼

ManagementObjectSearcher 示例

   class Program
   {
       static void Main(string[] args)
       {
           //
           Console.WriteLine("Physical disks: ");
           PrintManagementInfoProperty("Win32_DiskDrive", "Name");
           Console.WriteLine("*****************************");
           //
           Console.WriteLine("Logical disks: ");
           PrintManagementInfoProperty("Win32_LogicalDisk", "Name");
           Console.WriteLine("*****************************");
           //
           Console.WriteLine("Network adapters: ");
           PrintManagementInfoProperty("Win32_NetworkAdapter", "Name");
           Console.WriteLine("*****************************");
           //  
           Console.WriteLine("Processes: ");
           PrintManagementInfoProperty("Win32_Process", "Name");
           Console.WriteLine("*****************************");
           //  
           //wait for user input to keep console window up in debug mode
           Console.ReadLine();
       }
       static void PrintManagementInfoProperty(string WMIClassName, string WMIPropertyName)
       {
           ManagementObjectSearcher mos;
           //Get the specified property for all objects of the specified class
           mos = new ManagementObjectSearcher("SELECT " + WMIPropertyName + " FROM " + WMIClassName);
           foreach (ManagementObject mo in mos.Get())
           {
               PropertyData p = mo.Properties[WMIPropertyName]; 
               Console.WriteLine("{0}", p.Value);
           }
       }
   }
檢索有關所有網路連線的資訊
提供示例
檢索有關所有暫停的服務的資訊
以下程式碼使用 ManagementObjectSearcher 物件檢索所有正在執行的服務,然後顯示其名稱。
C# 示例程式碼

列舉服務示例

   class Program
   {
       static void Main(string[] args)
       {
           Console.WriteLine("Running services: ");
           //form the query, providing a WMI class name and a condition
           SelectQuery query = new SelectQuery("Win32_Service", "State='Running'");
           //find matching management objects
           ManagementObjectSearcher mos = new ManagementObjectSearcher(query);
           //
           foreach (ManagementObject mo in mos.Get())
           {
               Console.WriteLine(mo.Properties["Name"].Value);
           }
           //                        
           //wait for user input to keep console window up in debug mode
           Console.ReadLine();
       }
   }

ManagementQuery 類 - MSDN

EventQuery 類 - MSDN

ObjectQuery 類 - MSDN

使用 ManagementEventWatcher 類訂閱管理事件 - MSDN


序列化和輸入/輸出

考試目標: 在 .NET Framework 應用程式中實現序列化和輸入/輸出功能

主題

序列化

維基百科對序列化的定義是:“在資料儲存和傳輸的上下文中,序列化是將物件儲存到儲存介質(例如檔案或記憶體緩衝區)或透過網路連線鏈路以二進位制形式傳輸的過程”。

這裡解決的問題是,物件是由正在執行的程序建立的,因此與該程序例項的生命週期繫結在一起。如果由於任何原因,而且有很多原因,您想要在另一個程序例項的上下文中“傳輸”該物件,那麼您就會遇到問題,您可以透過在原始程序中“儲存”物件的 state 並在目標程序中“恢復”它來解決。這個“儲存”部分稱為序列化,而“恢復”部分稱為反序列化。

可序列化屬性

如果物件的類名字首為 [Serializable] 屬性,則該物件是可序列化的。

物件序列化

可以使用 BinaryFormatter 類 來序列化物件。要序列化,請使用 BinaryFormatter 的 Serialize() 方法,該方法將流和可序列化物件作為引數。要反序列化,請使用 BinaryFormatter 的 Deserialize() 方法,該方法將流作為引數並返回一個可以強制轉換為原始物件型別的物件。請記住,在使用完流後透過呼叫流的 Close() 方法來關閉流。

XML 序列化

可以使用 XmlSerializer 類 來序列化物件。要序列化,請使用 XmlSerializer 的 Serialize() 方法,該方法將流和可序列化物件作為引數。要反序列化,請使用 XmlSerializer 的 Deserialize() 方法,該方法將流作為引數並返回一個可以強制轉換為原始物件型別的物件。請記住,在使用完流後透過呼叫流的 Close() 方法來關閉流。

有關 XML 和 SOAP 序列化的概述,請參閱 MSDN

自定義序列化

ISerializable 介面允許物件控制自己的序列化和反序列化。

閱讀器

編寫器

格式化程式

格式化程式用於將物件序列化到流中。

檔案 I/O

管理位元組流

壓縮

隔離儲存

有關隔離儲存任務的一般討論,請參閱 MSDN

類、介面和工具

序列化和反序列化

考試目標: 使用執行時序列化技術序列化或反序列化物件或物件圖。

(參考 System.Runtime.Serialization 名稱空間)

序列化介面

IDeserializationCallback 介面 - MSDN

IFormatter 介面和 IFormatterConverter 介面

IFormatter 介面 - MSDN
IFormatterConverter 介面 - MSDN

ISerializable 介面 - MSDN

序列化屬性
有關一些序列化屬性示例,請參閱 MSDN

OnDeserializedAttribute 類和 OnDeserializingAttribute 類

OnDeserializedAttribute 類 - MSDN
OnDeserializingAttribute 類 - MSDN

OnSerializedAttribute 類和 OnSerializingAttribute 類

OnSerializedAttribute 類 - MSDN
OnSerializingAttribute 類 - MSDN

OptionalFieldAttribute 類 - MSDN

SerializationEntry 結構和 SerializationInfo 類

SerializationEntry 結構 - MSDN

SerializationInfo 類 - MSDN

ObjectManager 類

ObjectManager 類 - MSDN

Formatter 類、FormatterConverter 類和 FormatterServices 類

Formatter 類 - MSDN

FormatterConverter 類 - MSDN

FormatterServices 類 - MSDN

StreamingContext 結構

StreamingContext 結構 - MSDN

XML 序列化

考試目標: 使用 System.Xml.Serialization 名稱空間控制物件以 XML 格式序列化。

XmlSerializer 類 - MSDN

考試目標:使用 XmlSerializer 類將物件序列化和反序列化為 XML 格式

使用序列化屬性控制序列化 - MSDN

有關控制序列化的屬性列表,請參閱 MSDN

實現 XML 序列化介面以提供 XML 序列化的自定義格式 - MSDN

System.Xml.Serialization 名稱空間提供委託和事件處理程式 - MSDN

自定義序列化

考試目標: 使用序列化格式化程式類實現自定義序列化格式。

SoapFormatter 類 - MSDN

(參考 System.Runtime.Serialization.Formatters.Soap 名稱空間)

BinaryFormatter 類 - MSDN

(參考 System.Runtime.Serialization.Formatters.Binary 名稱空間

檔案系統類

考試目標: 使用檔案系統類訪問檔案和資料夾。

(參考 System.IO 名稱空間)

File 類和 FileInfo 類

有關常見的 I/O 任務,請參閱 MSDN
檔案類 - MSDN
FileInfo 類 - MSDN

目錄類和 DirectoryInfo 類

目錄類 - MSDN
DirectoryInfo 類 - MSDN

DriveInfo 類和 DriveType 列舉

DriveInfo 類 - MSDN
DriveType 列舉 - MSDN

FileSystemInfo 類和 FileSystemWatcher 類

FileSystemInfo 類
FileSystemWatcher 類
FileSystemWatcher 類旨在檢測檔案系統中的更改。
它可以使用 Filter 和 Path 屬性進行引數化。
  Example: 
  FileSystemWatcher w = new FileSystemWatcher();
  w.Filter = "*.txt";
  w.Path = @"C:\Windows";
Filter 屬性僅用於檢查檔名的模式。因此,不要在其中使用目錄路徑。
您可以新增 WaitForChanged(..) 等方法來監視指定區域的更改。

Path 類 - MSDN

System.IO.Path 類具有許多用於建立和解析資源路徑的 有用的靜態方法

ErrorEventArgs 類和 ErrorEventHandler 委託

ErrorEventArgs 類 - MSDN
ErrorEventHandler 委託 - MSDN

RenamedEventArgs 類和 RenamedEventHandler 委託

RenamedEventArgs 類 - MSDN
RenamedEventHandler 委託 - MSDN

位元組流

考試目標:使用 Stream 類管理位元組流。

(參考 System.IO 名稱空間)

FileStream 類 - MSDN

Stream 類 - MSDN

System.IO.Stream 是所有其他流繼承的抽象基類。無法例項化 Stream 類。而是使用從 Stream 派生的其他類之一。
就 70-536 考試目標而言,從 Stream 繼承的最重要的類是
  • System.IO.FileStream
  • System.IO.MemoryStream
  • System.IO.Compression.DeflateStream
  • System.IO.Compression.GZipStream
  • System.Security.Cryptography.CryptoStream
  • System.IO.BufferedStream
有關從 Stream 繼承的類的完整列表,請參見 MSDN
有關檔案和流 I/O 的討論,請參見 MSDN

MemoryStream 類 - MSDN

BufferedStream 類 - MSDN

Reader 和 Writer 類

考試目標:使用 Reader 和 Writer 類管理 .NET Framework 應用程式資料。

(參考 System.IO 名稱空間)

StringReader 類和 StringWriter 類 - MSDNMSDN

StringReader 和 StringWriter 繼承自 TextReader/TextWriter。
  • StringReader 是字串的 TextReader。
  • StringWriter 是字串的 TextWriter。

TextReader 類和 TextWriter 類

TextReader 類 - MSDN
TextReader 和 TextWriter 是抽象基類,StreamReader、StreamWriter、StringReader 和 StringWriter 從中派生。StreamReader 和 StringReader 從 TextReader 派生。StreamWriter 和 StringWriter 從 TextWriter 派生。
TextWriter 類 - MSDN

StreamReader 類和 StreamWriter 類 - MSDNMSDN

StreamReader 和 StreamWriter 類提供用於讀寫基於字元的流的基本功能 (ReadLine()、WriteLine()、ReadToEnd())。
StreamReader 和 StreamWriter 繼承自抽象類 TextReader 和 TextWriter
  • StreamReader 是流的 TextReader。
  • StreamWriter 是流的 TextWriter。
StreamReader 的 Peek 和 Read 方法
  • Peek 方法獲取特定位置的字元,但不前進。
  • Read 方法獲取特定位置的字元並前進。

BinaryReader 類和 BinaryWriter 類

BinaryReader 類 - MSDN
BinaryWriter 類 - MSDN

壓縮和隔離儲存

考試目標:壓縮或解壓縮 .NET Framework 應用程式中的流資訊,並透過使用隔離儲存來提高應用程式資料的安全性。

(參考 System.IO.Compression 名稱空間)

(參考 System.IO.IsolatedStorage 名稱空間)

IsolatedStorageFile 類 - MSDN

IsolatedStorageFileStream 類 - MSDN

DeflateStream 類 - MSDN

GZipStream 類 - MSDN


安全

考試目標:透過使用 .NET Framework 2.0 安全功能來提高 .NET Framework 應用程式的安全性

主題

程式碼訪問安全

程式碼訪問安全 (CAS) 允許控制授予特定託管應用程式的各種許可權。 MSDN

許可權允許訪問系統資源。許可權集是許可權的集合。程式碼組將一個許可權集與一個證據型別關聯起來。證據用於識別程式集。證據型別可以包括應用程式目錄、程式集的加密雜湊、釋出者的數字簽名、下載程式集的站點、程式集的加密強名稱、下載程式集的 URL 以及程式集執行的安全區域。安全區域包括計算機區域、本地 Intranet 區域、Internet 區域、受信任站點和不受信任站點。請參閱 Internet Explorer 中的“Internet 選項”安全選項卡,以檢視各種安全區域。一個程式集可以與多個程式碼組關聯。許可權集可以與多個程式碼組關聯。

安全策略是程式碼組和許可權集的邏輯分組。不受信任的託管程式集必須透過四個安全策略:企業安全策略、機器安全策略、使用者安全策略和應用程式域安全策略。這些安全策略中的任何一個都可以拒絕不受信任的託管程式集的許可權。

類、介面和工具

實現程式碼訪問安全

考試目標:實現程式碼訪問安全以提高 .NET Framework 應用程式的安全性。

(參考 System.Security 名稱空間)

SecurityManager 類 - MSDN

CodeAccessPermission 類 - MSDN

使用程式碼訪問安全策略工具 (Caspol.exe) 在機器、使用者和企業策略級別修改程式碼訪問安全策略 - MSDN

PermissionSet 類、NamedPermissionSet 類和 PermissionSetCollection 類

PermissionSet 類 - MSDN
NamedPermissionSet 類 - MSDN
PermissionSetCollection 類
似乎沒有這樣的東西,需要調查…

標準安全介面

IEvidenceFactory 介面 - MSDN
IPermission 介面 - MSDN

實現訪問控制

考試目標:使用 System.Security.AccessControl 類實現訪問控制。

DirectorySecurity 類、FileSecurity 類、FileSystemSecurity 類和 RegistrySecurity 類

DirectorySecurity 類 - MSDN
FileSecurity 類 - MSDN
FileSystemSecurity 類 - MSDN
RegistrySecurity 類 - MSDN

AccessRule 類 - MSDN

AuthorizationRule 類和 AuthorizationRuleCollection 類

AuthorizationRule 類 - MSDN
AuthorizationRuleCollection 類 - MSDN

CommonAce 類、CommonAcl 類、CompoundAce 類、GenericAce 類和 GenericAcl 類

CommonAce 類 - MSDN
CommonAcl 類 - MSDN
CompoundAce 類 - MSDN
GenericAce 類 - MSDN
GenericAcl 類 - MSDN

AuditRule 類 - MSDN

MutexSecurity 類、ObjectSecurity 類和 SemaphoreSecurity 類

MutexSecurity 類 - MSDN
ObjectSecurity 類 - MSDN
SemaphoreSecurity 類 - MSDN

實現自定義身份驗證方案

考試目標: 使用 System.Security.Authentication 類實現自定義身份驗證方案。

(參考 System.Security.Authentication 名稱空間 - MSDN)

有關自定義身份驗證方案的參考,請參見 MSDN

加密、解密和雜湊資料

考試目標: 使用 System.Security.Cryptography 類加密、解密和雜湊資料。

(參考 System.Security.Cryptography 名稱空間)

DES 類和 DESCryptoServiceProvider 類

DES 類 - MSDN
DESCryptoServiceProvider 類 - MSDN

HashAlgorithm 類 - MSDN

DSA 類和 DSACryptoServiceProvider 類

DSA 類 - MSDN
DSACryptoServiceProvider 類 - MSDN

SHA1 類和 SHA1CryptoServiceProvider 類

SHA1 類 - MSDN
SHA1CryptoServiceProvider 類 - MSDN

TripleDES 和 TripleDESCryptoServiceProvider 類

TripleDES - MSDN
TripleDESCryptoServiceProvider 類 - MSDN

MD5 類和 MD5CryptoServiceProvider 類

MD5 類 - MSDN
MD5CryptoServiceProvider 類 - MSDN

RSA 類和 RSACryptoServiceProvider 類

RSA 類 - MSDN
RSACryptoServiceProvider 類 - MSDN

RandomNumberGenerator 類 - MSDN

CryptoStream 類 - MSDN

CryptoConfig 類 - MSDN

RC2 類和 RC2CryptoServiceProvider 類

RC2 類 - MSDN
RC2CryptoServiceProvider 類 - MSDN

AssymetricAlgorithm 類 MSDN

ProtectedData 類和 ProtectedMemory 類

ProtectedData 類 - MSDN
ProtectedMemory 類 - MSDN

RijndaelManaged 類和 RijndaelManagedTransform 類

RijndaelManaged 類 - MSDN
RijndaelManagedTransform 類 - MSDN

CspParameters 類 - MSDN

CryptoAPITransform 類 - MSDN

基於雜湊的訊息認證碼 (HMAC) - MSDN

HMACMD5 類 - MSDN
HMACRIPEMD160 類 - MSDN
HMACSHA1 類 - MSDN
HMACSHA256 類 - MSDN
HMACSHA384 類 - MSDN
HMACSHA512 類 - MSDN

控制權限

考試目標: 使用 System.Security.Permission 類控制資源的許可權。

(參考 System.Security.Permission 名稱空間)

SecurityPermission 類 - MSDN

PrincipalPermission 類 - MSDN

FileIOPermission 類 - MSDN

您也可以在程式集級別或類級別設定 FileIoPermisson 屬性。 然後注意 SecurityAction 列舉。
  • SecurityAction.RequestRefuse:指定不應授予的操作。
  • SecurityAction.RequestMinumum:請求一組最小許可權。 如果未提供,應用程式將無法執行。

StrongNameIdentityPermission 類 - MSDN

UIPermission 類 - MSDN

UrlIdentityPermission 類 - MSDN

PublisherIdentityPermission 類 - MSDN

GacIdentityPermission 類 - MSDN

FileDialogPermission 類 - MSDN

DataProtectionPermission 類 - MSDN

EnvironmentPermission 類 - MSDN

IUnrestrictedPermission 介面 - MSDN

RegistryPermission 類 - MSDN

IsolatedStorageFilePermission 類 - MSDN

KeyContainerPermission 類 - MSDN

ReflectionPermission 類 - MSDN

StorePermission 類 - MSDN

SiteIdentityPermission 類 - MSDN

ZoneIdentityPermission 類 - MSDN

控制程式碼許可權

考試目標: 使用 System.Security.Policy 類控制程式碼許可權。

(參考 System.Security.Policy 名稱空間)

ApplicationSecurityInfo 類和 ApplicationSecurityManager 類

ApplicationSecurityInfo 類 - MSDN
ApplicationSecurityManager 類 - MSDN

ApplicationTrust 類和 ApplicationTrustCollection 類

ApplicationTrust 類 - MSDN
ApplicationTrustCollection 類 - MSDN

Evidence 類和 PermissionRequestEvidence 類

Evidence 類 - MSDN
PermissionRequestEvidence 類 - MSDN

CodeGroup 類、FileCodeGroup 類、FirstMatchCodeGroup 類、NetCodeGroup 類和 UnionCodeGroup 類

CodeGroup 類 - MSDN
FileCodeGroup 類 - MSDN
FirstMatchCodeGroup 類 - MSDN
NetCodeGroup 類 - MSDN
UnionCodeGroup 類 - MSDN

條件類

AllMembershipCondition 類 - MSDN
ApplicationDirectory 類和 ApplicationDirectoryMembershipCondition 類
ApplicationDirectory 類 - MSDN
ApplicationDirectoryMembershipCondition 類 - MSDN
GacMembership 類和 GacMembershipCondition 類
GacMembership 類
在 MSDN 上沒有搜尋結果!?這裡需要進行一些調查。
GacMembershipCondition 類 - MSDN
Hash 類和 HashMembershipCondition 類
Hash 類 - MSDN
HashMembershipCondition 類 - MSDN
Publisher 類和 PublisherMembershipCondition 類
Publisher 類 - MSDN
PublisherMembershipCondition 類 - MSDN
Site 類和 SiteMembershipCondition 類
Site 類 - MSDN
SiteMembershipCondition 類 - MSDN
StrongName 類和 StrongNameMembershipCondition 類
StrongName 類 - MSDN
StrongNameMembershipCondition 類 - MSDN
Url 類和 UrlMembershipConditon 類
Url 類 - MSDN
UrlMembershipConditon 類 - MSDN
Zone 類和 ZoneMembershipCondition 類
Zone 類 - MSDN
ZoneMembershipCondition 類 - MSDN

PolicyLevel 類和 PolicyStatement 類

PolicyLevel 類 - MSDN
PolicyStatement 類 - MSDN

IApplicationTrustManager 介面、IMembershipCondition 介面和 IIdentityPermissionFactory 介面

IApplicationTrustManager 介面 - MSDN
IMembershipCondition 介面 - MSDN
IIdentityPermissionFactory 介面 - MSDN

訪問和修改身份資訊

考試目標:使用 System.Security.Principal 類訪問和修改身份資訊。

(參考 System.Security.Principal 名稱空間)

GenericIdentity 類和 GenericPrincipal 類

GenericIdentity 類 - MSDN
GenericPrincipal 類 - MSDN

WindowsIdentity 類和 WindowsPrincipal 類

WindowsIdentity 類 - MSDN
WindowsPrincipal 類 - MSDN

NTAccount 類和 SecurityIdentifier 類

NTAccount 類 - MSDN
SecurityIdentifier 類 - MSDN

IIdentity 介面和 IPrincipal 介面

IIdentity 介面 - MSDN
IPrincipal 介面 - MSDN

WindowsImpersonationContext 類 - MSDN

IdentityReference 類和 IdentityReferenceCollection 類

IdentityReference 類 - MSDN
IdentityReferenceCollection 類 - MSDN


互操作性、反射和郵件

考試目標:在 .NET Framework 應用程式中實現互操作性、反射和郵件功能


主題

互操作性

反射

維基百科對計算機科學中反射的定義是:“一個計算機程式可以觀察和修改自身結構和行為的過程。由反射驅動的程式設計正規化稱為反射式程式設計”。

該概念在 .NET 中得到廣泛實現,因為許多資訊都在公共中間語言 (CIL) 級別維護。

除其他事項外,您可以

  • 獲取有關正在執行的程式集或程式集組成部分(模組、類、方法等)的資訊,這對動態載入的程式集(在編譯時未知)特別有用。
  • 在程式碼中放置自定義屬性並在執行時檢索這些屬性。
  • 動態呼叫方法。
  • 在執行時“發出”和執行 CIL 程式碼。

郵件

類、介面和工具

COM 互操作性

考試目標:將 COM 元件公開給 .NET Framework 以及將 .NET Framework 元件公開給 COM

(參考 System.Runtime.InteropServices 名稱空間)

將 COM 元件公開給 .NET Framework

首先,.NET 開發人員指南中有一篇文章涵蓋了本節的第一部分,請參閱 MSDN

將型別庫匯入為程式集 - MSDN

新增對型別庫的引用
與上述連結相同,請參閱第二段。
型別庫匯入器 (Tlbimp.exe) - MSDN
從型別庫生成互操作程式集 - MSDN
匯入的庫轉換 - MSDN
匯入的模組轉換 - MSDN
匯入的型別轉換 - MSDN
匯入的成員轉換 - MSDN
匯入的引數轉換 - MSDN
TypeConverter 類 - MSDN

在託管程式碼中建立 COM 型別 - MSDN

編譯互操作專案 - MSDN

部署互操作應用程式 - MSDN

將 .NET Framework 元件公開給 COM

該部分的其餘目標直接參考 MSDN

為互操作性限定 .NET Framework 型別 - MSDN

應用互操作屬性,例如 ComVisibleAttribute 類 - MSDN

為 COM 打包程式集 - MSDN

部署供 COM 訪問的應用程式 - MSDN

呼叫 DLL 函式

考試目標:在 .NET Framework 應用程式中呼叫非託管 DLL 函式,並在 .NET Framework 應用程式中控制資料的封送處理。

(參考 System.Runtime.InteropServices 名稱空間)

平臺呼叫 - MSDN

建立類以儲存 DLL 函式 - MSDN

在託管程式碼中建立原型 - MSDN

DllImportAttribute 類 - MSND

呼叫 DLL 函式 - MSDN

在網際網路上查詢“P/Invoke”。
一個很好的參考資料是:http://www.pinvoke.net/
一些簡短的提示
  • 對於 DllAttribute 的“CharSet”,從邏輯上講,CharSet.Auto 是最安全的方式,因為它會自動檢測使用的字元集。字元集可以是 ANSI(Win95/Win98/ME) 或 Unicode (Win2000/WinXP)
  • GetEntryPoint:可能有一個棘手的問題,因為 EntryPoint 指示應呼叫的方法。如果使用的方法名稱與要呼叫的方法名稱不同,則需要此標誌。

在特殊情況下呼叫 DLL 函式,例如傳遞結構和實現回撥函式

與上述連結相同
傳遞結構 - MSDN
實現回撥函式 - MSDN

建立新的 Exception 類並將其對映到 HRESULT - MSDN

預設封送處理行為 - MSDN

使用平臺呼叫封送處理資料 - MSDN

使用 COM 互操作性封送處理資料 - MSDN

MarshalAsAttribute 類和 Marshal 類

MarshalAsAttribute 類 - MSDN
Marshal 類 - MSDN

實現反射

考試目標:在 .NET Framework 應用程式中實現反射功能(參考 System.Reflection 名稱空間),並使用 System.Reflection.Emit 名稱空間建立元資料、Microsoft 中間語言 (MSIL) 和 PE 檔案。

構建自定義屬性(附錄)

Assembly 類 -MSDN

程式集屬性 - MSDN

AssemblyAlgorithmIdAttribute 類 - MSDN
AssemblyCompanyAttribute 類 - MSDN
AssemblyConfigurationAttribute 類 - MSDN
AssemblyCopyrightAttribute 類 - MSDN
AssemblyCultureAttribute 類 - MSDN
AssemblyDefaultAliasAttribute 類 - MSDN
AssemblyDelaySignAttribute 類 - MSDN
AssemblyDescriptionAttribute 類 - MSDN
AssemblyFileVersionAttribute 類 - MSDN
AssemblyFlagsAttribute 類 - MSDN
AssemblyInformationalVersionAttribute 類 - MSDN
AssemblyKeyFileAttribute 類 - MSDN
AssemblyTitleAttribute 類 - MSDN
AssemblyTrademarkAttribute 類 - MSDN
AssemblyVersionAttribute 類 - MSDN

資訊類

ConstructorInfo 類 - MSDN
MethodInfo 類 - MSDN
MemberInfo 類 - MSDN
PropertyInfo 類 - MSDN
FieldInfo 類 - MSDN
EventInfo 類 - MSDN
LocalVariableInfo 類 - MSDN

Binder 類和 BindingFlags - MSDN

MethodBase 類和 MethodBody 類

MethodBase 類 - MSDN
MethodBody 類 - MSDN

生成器類

AssemblyBuilder 類 - MSDN
ConstructorBuilder 類 - MSDN
EnumBuilder 類 - MSDN
EventBuilder 類 - MSDN
FieldBuilder 類 - MSDN
LocalBuilder 類 - MSDN
MethodBuilder 類 - MSDN
ModuleBuilder 類 - MSDN
ParameterBuilder 類 - MSDN
PropertyBuilder 類 - MSDN
TypeBuilder 類 - MSDN

傳送電子郵件

考試目標:從 .NET Framework 應用程式向簡單郵件傳輸協議 (SMTP) 伺服器傳送電子郵件以進行傳送。

(參考 System.Net.Mail 名稱空間)

MailMessage 類 - MSDN

MailAddress 類和 MailAddressCollection 類

MailAddress 類 - MSDN
MailAddressCollection 類 - MSDN

SmtpClient 類、SmtpPermission 類和 SmtpPermissionAttribute 類

SmtpClient 類 - MSDN
SmtpPermission 類 - MSDN
SmtpPermissionAttribute 類 - MSDN

Attachment 類、AttachmentBase 類和 AttachmentCollection 類

Attachment 類 - MSDN
AttachmentBase 類 - MSDN
AttachmentCollection 類 - MSDN

SmtpException 類、SmtpFailedReceipientException 類和 SmtpFailedReceipientsException 類

SmtpException 類 - MSDN
SmtpFailedReceipientException 類 - MSDN
請注意,考試目標頁面中存在一個拼寫錯誤,他們使用的是 SmtpFailedReceipientException,而不是 SmtpFailedRecipientException。
SmtpFailedRecipientsException 類 - MSDN
與上面相同的拼寫錯誤

SendCompleteEventHandler 委託 - MSDN

LinkedResource 類和 LinkedResourceCollection 類

LinkedResource 類 - MSDN
LinkedResourceCollection 類 - MSDN

AlternateView 類和 AlternateViewCollection 類

AlternateView 類 - MSDN
AlternateViewCollection 類 - MSDN



全球化、繪圖和文字操作

考試目標:在 .NET Framework 應用程式中實現全球化、繪圖和文字操作功能。

主題

全球化

繪圖

文字操作

在考試目標的上下文中,文字操作涵蓋了 3 個主要主題:字串構建、正則表示式和文字編碼。我們在以下段落中分別介紹它們。

String 和 StringBuilder 類

文字操作從字串表示開始,字串表示透過 String 類 完成。沒有特定的考試目標提及 String 類,但我們添加了一個部分,因為您必須瞭解它的一些特定特性。

接下來是 StringBuilder 類,它用於有效地構建字串。

正則表示式

RegexMatchGroup 類共同實現了 .NET 框架中的正則表示式支援。

正則表示式本身就是一個世界,並且已經存在很長時間了。

有一個關於 正則表示式 的華夏公益教科書,它除了其他內容外,還指向這個 教程

.NET 中的正則表示式支援基本上允許

  • 測試字串與正則表示式模式的匹配(Regex.IsMatch 方法)
  • 提取與模式一部分“匹配”的子字串(使用 Match 和 Group 類呼叫 Regex.Match 方法)。
文字編碼

類、介面和工具

根據文化資訊格式化資料。

(參考 System.Globalization 名稱空間)

訪問文化和區域資訊

考試目標:在 .NET Framework 應用程式中訪問文化和區域資訊。

CultureInfo 類 - MSDN

CultureTypes 列舉 - MSDN

RegionInfo 類 - MSDN

根據文化格式化日期和時間值。

DateTimeFormatInfo 類 - MSDN

根據文化格式化數字值。

NumberFormatInfo 類 - MSDN

NumberStyles 列舉 - MSDN

執行文化敏感的字串比較。

CompareInfo 類 - MSDN

CompareOptions 列舉 - MSDN

自定義文化

考試目標:基於現有的文化和區域類構建自定義文化類。

CultureAndRegionInfoBuilder 類 - MSDN

CultureAndRegionModifier 列舉 - MSDN

System.Drawing 名稱空間

考試目標:透過使用 System.Drawing 名稱空間來增強 .NET Framework 應用程式的使用者介面。

畫筆、鋼筆、顏色和字型

考試目標:透過使用畫筆、鋼筆、顏色和字型來增強 .NET Framework 應用程式的使用者介面。

Brush 類 - MSDN

Brushes 類 - MSDN

SystemBrushes 類 - MSDN

TextureBrush 類 - MSDN

Pen 類 - MSDN

Pens 類 - MSDN

SystemPens 類 - MSDN

SolidBrush 類 - MSDN

Color 結構 - MSDN

ColorConverter 類 - MSDN

ColorTranslator 類 - MSDN

SystemColors 類 - MSDN

StringFormat 類 - MSDN

Font 類 - MSDN

FontConverter 類 - MSDN

FontFamily 類 - MSDN

SystemFonts 類 - MSDN

圖形、影像、點陣圖和圖示

考試目標:透過使用圖形、影像、點陣圖和圖示來增強 .NET Framework 應用程式的使用者介面

Graphics 類 - MSDN

BufferedGraphics 類 - MSDN

BufferedGraphicsManager 類 - MSDN

Image 類 - MSDN

ImageConverter 類 - MSDN

ImageAnimator 類 - MSDN

Bitmap 類 - MSDN

Icon 類 - MSDN

IconConverter 類 - MSDN

SystemIcons 類 - MSDN

形狀和大小

考試目標:透過使用形狀和大小來增強 .NET Framework 應用程式的使用者介面

Point 結構 - MSDN

PointConverter 類 - MSDN

Rectangle 結構 - MSDN

RectangleConverter 類 - MSDN

Size 結構 - MSDN

SizeConverter 類 - MSDN

Region 類 - MSDN

文字處理和正則表示式

考試目標:增強 .NET Framework 應用程式的文字處理功能,並使用正則表示式在 .NET Framework 應用程式中搜索、修改和控制文字

(參考 System.Text 名稱空間)

(參考 System.RegularExpressions 名稱空間)

String 類

String 類不是具體的考試目標,但我們在這裡討論了一些它的特性。

String 類 - MSDN

StringBuilder 類

StringBuilder 類用於非常快速的字串連線。如果您使用傳統的字串連線,它將執行非常慢,因為字串儲存在陣列中。每次連線都會導致陣列增加其大小,並且記憶體必須在內部複製到新位置。這非常慢。

為了快速字串連線,請使用 StringBuilder。它快了大約 1000 倍(取決於您連線的字串)。

StringBuilder 類 - MSDN


請參考示例以衡量效能差異。

C# 示例

StringBuilder 示例

using System;
using System.Collections;
public class Demo
{
    public static void Main()
    {
        const int len = 30;
        const int loops = 5000;
        //        
        DateTime timeStart, timeStop;
        //         
        // Measure time for normal string concatenation
        timeStart = DateTime.Now;
        string str = "";
        for (int i = 0; i < loops; i++)
        {
            str += new String('x', len);
        }
        timeStop = DateTime.Now;
        int millis = timeStop.Subtract(timeStart).Milliseconds;
        Console.WriteLine("Duration for " + loops + " loops: " + millis + " ms");
        //         
        // Measure time for StringBuilder string concatenation
        StringBuilder sb = new StringBuilder();
        timeStart = DateTime.Now;
        for (int i = 0; i < loops; i++)
        {
            sb.Append(new String('x', len));
        }
        str = sb.ToString();
        timeStop = DateTime.Now;
        millis = timeStop.Subtract(timeStart).Milliseconds;
        Console.WriteLine("Duration for " + loops + " loops: " + millis + " ms");
        //         
        Console.ReadLine();
    }
}
Regex 類

Regex 類 - MSDN

Match 類和 MatchCollection 類

Match 類 - MSDN

MatchCollection 類 - MSDN

Group 類和 GroupCollection 類

Group 類 - MSDN

GroupCollection 類 - MSDN

使用 Encoding 類編碼文字

Encoding 類 - MSDN

EncodingInfo 類 - MSDN

ASCIIEncoding 類 - MSDN

UnicodeEncoding 類 - MSDN

UTF8Encoding 類 - MSDN

Encoding 回退類 - MSDN

使用 Decoding 類解碼文字。

Decoder 類 - MSDN

Decoder 回退類 - MSDN

Capture 類和 CaptureCollection 類

Capture 類 - MSDN

CaptureCollection 類 - MSDN

另請參閱

參考

70-536 培訓資料

.NET Framework 應用程式開發基礎自學培訓資料,第二版 Tony Northrup Microsoft Press

我們將其稱為“培訓資料”,因為它被 Microsoft 推薦為 70-536 考試的培訓輔助資料(例如,請參見 MCPD)。

如果您使用培訓資料,您可能想要檢視 這裡 以瞭解已知的更正列表。本華夏公益教科書中列出了其他可能的更正。

C Sharp 2005

Visual C#: The language - 2005 Edition Donis Marchall Microsoft Press

也推薦作為 70-536 考試的培訓輔助資料(例如,請參見 MCPD)。

ECMA 335

如果您想更深入地瞭解通用語言基礎設施的定義,可以下載 官方規範

這對於考試來說不是必需的,但我們會使用規範幾次來澄清 MSDN 文件或其他參考手冊中的歧義。


附錄

附錄是一系列關於特定主題的文章,它們被正文引用,但沒有整合在正文中。


附錄:泛型型別

簡介

為了理解泛型,我們首先應該看看為什麼要使用它們。這最好用一個簡單的例子來解釋。假設我們想要建立一個客戶集合,這是我們經常做的事情。我們使用一個簡單的 Client 類,它具有姓名和帳號。通常,ArrayList 用於像這樣在記憶體中儲存多個客戶

  /// <summary>
  /// Representation of a client
  /// </summary>
  public class Client
  {
      private string _name;
      private string _accountNumber;
       
      /// <summary>
      /// Gets or sets the account number.
      /// </summary>
      /// <value>The account number.</value>
      public string AccountNumber
      {
          get { return _accountNumber; }
          set { _accountNumber = value; }
      }
       
      /// <summary>
      /// Gets or sets the name.
      /// </summary>
      /// <value>The name.</value>
      public string Name
      {
          get { return _name; }
          set { _name = value; }
      }
       
      /// <summary>
      /// Initializes a new instance of the <see cref="T:Client"/> class.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="accountNumber">The account number.</param>
      public Client(string name, string accountNumber)
      {
          _name = name;
          _accountNumber = accountNumber;
      }
       
      /// <summary>
      /// The Main entry point of the console application
      /// </summary>
      /// <param name="args">The command line arguments</param>
      static void Main(string[] args)
      {
          ArrayList clients = new ArrayList();
          clients.Add(new Client("Marco", "332-3355"));
          clients.Add(new Client("Martinus", "453-5662"));
          foreach (Client client in clients)
          {
              Console.WriteLine("The account {0} belongs to {1}", client.AccountNumber, client.Name);
          }
          Console.ReadLine();
      }
  }

這對大多數讀者來說可能很熟悉,但是使用 ArrayList 我們不是型別安全的,在我看來這是不好的。我們還需要在想要使用它做一些事情時對物件進行強制型別轉換(和取消型別轉換)。對於值型別,還有另一個問題,我們不斷地對列表中的物件進行裝箱和取消裝箱。

儲存客戶的更好方法是使用型別化集合,這將解決型別安全問題,我們也不必每次使用它時都強制型別轉換檢索到的物件。一個好方法是從 CollectionBase 類繼承一個物件,看起來像這樣

  /// <summary>
  /// Representation of a client
  /// </summary>
  public class Client
  {
      private string _name;
      private string _accountNumber;
       
      /// <summary>
      /// Gets or sets the account number.
      /// </summary>
      /// <value>The account number.</value>
      public string AccountNumber
      {
          get { return _accountNumber; }
          set { _accountNumber = value; }
      }
       
      /// <summary>
      /// Gets or sets the name.
      /// </summary>
      /// <value>The name.</value>
      public string Name
      {
          get { return _name; }
          set { _name = value; }
      }
       
      /// <summary>
      /// Initializes a new instance of the <see cref="T:Client"/> class.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="accountNumber">The account number.</param>
      public Client(string name, string accountNumber)
      {
          _name = name;
          _accountNumber = accountNumber;
      }
       
      /// <summary>
      /// The Main entry point of the console application
      /// </summary>
      /// <param name="args">The command line arguments</param>
      static void Main(string[] args)
      {
          ClientList clients = new ClientList();
          clients.Add(new Client("Marco", "332-3355"));
          clients.Add(new Client("Martinus", "453-5662"));
          foreach (Client client in clients)
          {
              Console.WriteLine("The account {0} belongs to {1}", client.AccountNumber, client.Name);
          }
          Console.ReadLine();
      }
  }
           
  /// <summary>
  /// A list of clients
  /// </summary>
  public class ClientList : CollectionBase
  {
      /// <summary>
      /// Adds the specified client to the list.
      /// </summary>
      /// <param name="client">The client.</param>
      /// <returns></returns>
      public int Add(Client client)
      {
          return List.Add(client);
      }
       
      /// <summary>
      /// Gets or sets the <see cref="T:Client"/> at the specified index.
      /// </summary>
      /// <value></value>
      public Client this[int index]
      {
          get
          {
              return (Client)List[index];
          }
          set
          {
              List[index] = value;
          }
      }
  }


這看起來比我們第一個例子中使用 ArrayList 要好得多,而且通常是一個好方法。但是如果您的應用程式發展,並且我們獲得了更多想要儲存在集合中的型別,例如 Account 或 bank。使用 1.1 框架,我們必須為我們使用的每種型別的物件建立一個新的集合類,或者退回到醜陋的 ArrayList 方法。但是,使用新的 2.0 框架,MS 添加了泛型。這使得建立使用型別引數的類和方法成為可能。這允許開發人員建立在定義和例項化類時推遲某些型別規範的類和方法。透過使用泛型型別引數,開發人員可以編寫其他人可以使用而不會冒用未型別化類(如 ArrayList)所帶來的風險的類,並且與建立型別化集合相比,減少了開發人員需要做的工作量。所以,讓我們看看在使用框架中的泛型 List<T> 類時程式碼是什麼樣的。

  /// <summary>
  /// Representation of a client
  /// </summary>
  public class Client
  {
      private string _name;
      private string _accountNumber;
       
      /// <summary>
      /// Gets or sets the account number.
      /// </summary>
      /// <value>The account number.</value>
      public string AccountNumber
      {
          get { return _accountNumber; }
          set { _accountNumber = value; }
      }
       
      /// <summary>
      /// Gets or sets the name.
      /// </summary>
      /// <value>The name.</value>
      public string Name
      {
          get { return _name; }
          set { _name = value; }
      }
       
      /// <summary>
      /// Initializes a new instance of the <see cref="T:Client"/> class.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="accountNumber">The account number.</param>
      public Client(string name, string accountNumber)
      {
          _name = name;
          _accountNumber = accountNumber;
      }
       
      /// <summary>
      /// The Main entry point of the console application
      /// </summary>
      /// <param name="args">The command line arguments</param>
      static void Main(string[] args)
      {
          List<Client> clients = new List<Client>();
          clients.Add(new Client("Marco", "332-3355"));
          clients.Add(new Client("Martinus", "453-5662"));
          foreach (Client client in clients)
          {
              Console.WriteLine("The account {0} belongs to {1}", client.AccountNumber, client.Name);
          }
          Console.ReadLine();
      }
  }

感謝泛型,我們建立了一個型別安全的集合,就像我們通常例項化 ArrayList 一樣簡單,而無需編寫我們自己的型別化集合。現在我們已經簡要地瞭解瞭如何使用泛型來減少我們需要編寫的程式碼量,同時仍然使用型別化集合,讓我們看看如何建立我們自己的自定義泛型類。為了演示這一點,我建立了一個示例類,它繼承自 DictionaryBase 類。這個字典類的實現將接受 GUID 作為鍵,並將型別引數作為值型別。如果你和我一樣,你會使用 GUID 來標識資料庫中的記錄,所以使用它作為型別化集合中的鍵是我很喜歡做的事情。所以現在我有了可以用來儲存從資料庫中檢索資料時建立的所有物件的酷炫字典,我會給你程式碼。

  /// <summary>
  /// Representation of a client
  /// </summary>
  public class Client
  {
      private string _name;
      private string _accountNumber;
       
      /// <summary>
      /// Gets or sets the account number.
      /// </summary>
      /// <value>The account number.</value>
      public string AccountNumber
      {
          get { return _accountNumber; }
          set { _accountNumber = value; }
      }
       
      /// <summary>
      /// Gets or sets the name.
      /// </summary>
      /// <value>The name.</value>
      public string Name
      {
          get { return _name; }
          set { _name = value; }
      }
       
      /// <summary>
      /// Initializes a new instance of the <see cref="T:Client"/> class.
      /// </summary>
      /// <param name="name">The name.</param>
      /// <param name="accountNumber">The account number.</param>
      public Client(string name, string accountNumber)
      {
          _name = name;
          _accountNumber = accountNumber;
      }
       
      /// <summary>
      /// The Main entry point of the console application
      /// </summary>
      /// <param name="args">The command line arguments</param>
      static void Main(string[] args)
      {
          GuidDictionary<Client> clients = new GuidDictionary<Client>();
          Guid clientID1 = Guid.NewGuid();
          Guid clientID2 = Guid.NewGuid();
          clients.Add(clientID1, new Client("Marco", "332-3355"));
          clients.Add(clientID2, new Client("Martinus", "453-5662"));
          Console.WriteLine("The account {0} belongs to {1}", clients[clientID1].AccountNumber, clients[clientID1].Name);
          Console.WriteLine("The account {0} belongs to {1}", clients[clientID2].AccountNumber, clients[clientID2].Name);
          Console.ReadLine();
      }
  }
       
  public class GuidDictionary<T> : DictionaryBase
  {
      public void Add(Guid id, T item)
      {
          Dictionary.Add(id, item);
      }
       
      public T this[Guid id]
      {
          get
          {
              return (T)Dictionary[id];
          }
          set
          {
              Dictionary[id] = value;
          }
      }
  }

好吧,它可能不像你預期的那樣棒,標準字典中的許多方法甚至沒有實現,但是嘿,我必須留一些有趣的事情讓你這個讀者去做。

那麼我們在上面的程式碼中到底做了什麼?我們建立了一個由 Guid 索引的字典,並使用型別引數來限制我們的 add 方法和索引器。現在,當我們建立一個新類的例項並指定型別引數時,我們便擁有了一個型別安全的字典,該字典可用於按給定的 Guid 儲存和檢索物件的型別。

宣告中的 T 只是一個名稱,我們也可以使用 VeryLongNameForTheTypeParameter 而不是 T。好的,我們已經看到了如何使用型別引數來建立泛型類。但是在我們進入下一部分之前,讓我們看看 System.Collections.Generic.Dictionary<>。此類必須透過提供名為 TKey 和 TValue 的兩個型別引數來例項化。這表明我們可以在定義中使用多個型別引數,同時也表明構建自己的字典類是浪費時間,我可以很容易地使用像這樣泛型字典:Dictionary<Guid, Client> 並完成它。


附件:構建自定義屬性

此頁面原文由 William "Scott" Baker 撰寫

屬性摘要

屬性是一種“標記”程式碼元資料的元素的方法,它使用描述性資訊,這些資訊可以在執行時使用反射訪問。屬性必須直接或間接地從 System.Attribute 派生。.NET Framework 中存在大量的屬性;你也可以定義自己的屬性。在程式碼中使用屬性有三個方面

  1. 定義自定義屬性類,這涉及
    1. AttributeUsageAttribute屬性分配給你的類。
    2. 編寫程式碼以定義你的自定義屬性類。
    3. 為你的類建立引數。
  2. 將屬性分配給程式碼成員。
  3. 在執行時檢索屬性資訊。

建立自定義屬性類

如前所述,.NET Framework 中有幾個預定義的屬性;你可能已經在程式碼中使用過它們。特別是 XML 解析器在(反)序列化物件時嚴重依賴屬性。你也可以定義自己的自定義屬性,正如我們將在這裡展示的那樣。定義自定義屬性涉及三個步驟

  1. AttributeUsageAttribute屬性分配給你的類。
  2. 編寫程式碼以定義你的自定義屬性類。
  3. 為你的類建立引數。

"AttributeUsageAttribute"分配給你的類

你的屬性的“範圍”和其他特徵應透過使用AttributeUsageAttribute屬性來指定。
注意:在 Visual Basic 中,使用AttributeUsageAttribute屬性對於所有自定義屬性是必需的。將AttributeUsageAttribute屬性應用於類
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
public Class QualityCheckAttribute : System.Attribute 
{
  // ...
}

注意使用“AttributeUsage" 與 "。AttributeUsageAttribute". 按照慣例,所有屬性都以 "Attribute" 字尾命名 - 但在程式碼中使用時,可以省略字尾。這也適用於使用者定義的屬性;QualityCheckAttribute屬性可以作為以下兩種方式引用

[QualityCheck] // or... 
[QualityCheckAttribute]

AttributeUsageAttribute有三個成員ValidOn、AllowMultipleInherited.

  • ValidOn成員接受AttributeTargets列舉值,並將你的屬性限制為你指定的程式碼型別。預設值為AttributeTargets.All. 你可以將你的屬性限制為類、列舉、返回值或以下列表中的任何一個
All (any element)   Delegate    GenericParameter    Parameter
Assembly            Enum        Interface           Property
Class               Event       Method              ReturnValue
Constructor         Field       Module*             Struct

*Module refers to a portable executable (.exe or .dll), and not a Visual Basic standard module.

你也可以將目標型別組合為按位 OR 操作,以指定多個可接受的值

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
  • AllowMultiple是一個布林值,它確定是否可以將屬性多次應用於給定的成員。預設值為false. 以下示例說明了同一個屬性在程式碼元素上的多次例項
[QualityCheck("Scott Baker", "02/28/06", IsApproved = true,
   Comment = "This code follows all established guidelines.  Release approved.")]
[QualityCheck("Matt Kauffman", "01/15/06", IsApproved = false,
   Comment = "Code quality much improved. Minor revision required.")]
[QualityCheck("Joe Schmoe", 01/01/06", IsApproved = false,
   Comment = "This code is a mess and needs a complete rewrite")]
public class MyClass
{
// ... 
}
  • Inherited成員確定是否將繼承類中設定的屬性。預設值為 true
[AttributeUsage(AttributeTargets.Class)]
public class AttrOneAttribute : Attribute
{
  // ... 
}

// This attribute will not be inherited 
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class AttrTwoAttribute : Attribute
{
  // ... 
}

[AttrOne]
[AttrTwo]
public class ClassOne 
{
  // ... 
}

// This class inherits AttrOne from ClassOne, 
// but not AttrTwo 
public class ClassTwo : ClassOne
{
  // ... 
}

定義自定義屬性類

  • 屬性是繼承自System.Attribute的類,直接或間接繼承
public Class QualityCheckAttribute : System.Attribute // direct 
{
  // ...
}

public Class FinalCheck : QualityCheckAttribute  // indirect 
{
// ...
}
  • 屬性類具有AttributeUsageAttributeattribute
[AttributeUsage(AllowMultiple = true, Inherited = false)]
public Class QualityCheckAttribute : System.Attribute
{
  // ...
}

屬性。如前所述,使用AttributeUsageAttribute屬性在 VB 中是必需的。在 C# 中,如果未宣告,它將自動應用預設值。

為屬性建立引數

屬性接受兩種型別的引數:位置引數和命名引數。位置引數由類中的公共建構函式定義,必須按定義的順序排列,並且是必需的;命名引數由公共屬性定義,可以按任何順序排列,並且是可選的。
位置引數
屬性的位置引數由類中的建構函式定義。與任何類一樣,建構函式可以過載,並且可以定義不接受任何引數的預設建構函式。與任何其他類一樣,位置引數的簽名必須與屬性類中的建構函式的簽名匹配
public class QualityCheckAttribute : Attribute
{
  public QualityCheckAttribute(string Name, string Date)
  // ... 
}
命名引數
屬性的命名引數由類中定義的公共屬性定義。命名引數是可選的,並且宣告任何位置引數之後。該IsApproved屬性演示了一個命名引數
public class QualityCheckAttribute : Attribute
{
  private string _name;
  private string _date;
  private bool isApproved;
  public bool IsApproved
  {
    get {return isApproved;}
    set {isApproved = value;}
  }
  public QualityCheckAttribute(string Name, string Date)
  {
    // ...
  }
}

請記住,程式碼中的變數可以是位置引數,也可以是命名引數。如果要為_name_date 欄位新增公共屬性,我們可以將其用作命名引數或位置引數。當然,這不是一個推薦的做法:必需的引數應該是位置引數,可選引數應該是命名引數。

將屬性分配給程式碼成員

你已經看到了將屬性分配給程式碼成員的示例。但是,必須澄清一些要點。

  • 消歧義是指在程式碼成員上使用屬性的澄清。
  • 語法 - 有多種方法可以應用多個屬性。

消歧義

以下程式碼不清楚SomeAttribute是否應用於方法MyMethod或其return
public class MyAttribute : Attribute
{
  [SomeAttribute("Hello")]
  public string MyMethod(aString)
  {
    return aString;
  }
}

消歧義解決了這些問題。透過指定屬性應用到的程式碼型別,我們能夠解決混亂。以下程式碼顯示屬性應用於return

public class MyAttribute : Attribute
{
  [return : SomeAttribute]
  public string MyMethod(aString)
  {
    return aString;
  }
}

下表列出了允許使用屬性的所有宣告;對於每個宣告,在第二列中列出了該宣告上屬性的可能目標。以粗體顯示的目標是預設值。

Declaration               Possible targets
assembly                  assembly
module                    module
class                     type
struct                    type
interface                 type
enum                      type
delegate                  type, return
method                    method, return
parameter                 param
field                     field
property — indexer        property
property — get accessor   method, return
property — set accessor   method, param, return
event — field             event, field, method
event — property          event, property
event — add               method, param
event — remove            method, param

*Reference: Disambiguating Attribute Targets (C# Programming Guide)

人們可能會認為AttributeUsageAttribute 的 AttributeTargets在屬性定義中將有助於防止這種混淆:人們會錯了。編譯器在解決衝突時不會使用AttributeUsageAttribute資訊。即使你將屬性定義為僅應用於特定型別,例如AttributeTargets.Return,你仍然必須明確它應用於return型別,當應用屬性時,否則編譯器將使用預設目標method型別,並丟擲錯誤。

語法:應用多個屬性

當將多個屬性應用於成員時,有兩種方法可以做到這一點
[AttrOne(...), AttrTwo(...)]  
  // or...
[AttrOne(...)]
[AttrTwo(...)]

這兩種方法是等效的。請記住,如果你要在單個大括號中指定多個屬性,它們必須應用於相同的目標型別。如果沒有,你必須為每個型別提供單獨的宣告

[return : AttrOne(...), method : AttrTwo(...)]  // <-- invalid!
  // instead, you must...
[return : AttrOne(...)]
[method : AttrTwo(...)]

在執行時檢索屬性資訊

能夠宣告和應用屬性並不是很有用,除非我們可以檢索這些資料並使用它們。幸運的是,這是一個直接的過程。將解決三個基本場景

  1. 從成員中檢索單個屬性。
  2. 從成員中檢索多個屬性。
  3. 從多個成員中檢索單個型別的屬性。

從成員中檢索單個屬性

要訪問屬性資訊

  1. 宣告屬性型別的例項。
  2. 使用Attribute.GetCustomAttribute(type, typeof)方法將屬性讀入例項。
  3. 使用例項的屬性讀取值。

以下示例程式碼聲明瞭一個類ExampleClass,它具有QualityCheck屬性。該GetSingleAttribute方法接受目標成員型別和要查詢的屬性型別。該Attribute.GetCustomAttribute方法將屬性資訊檢索到attr物件中,我們可以在其中讀取非常重要的IsApprovedproperty

[QualityCheck("Scott Baker", "02/04/2006", IsApproved = false)]
public class ExampleClass
{

  public static void Main()
  {
    GetSingleAttribute(typeof(ExampleClass), typeof(QualityCheck))
  }
 
  public static void GetSingleAttribute(Type targetType, Type attrType)
  {
    typeof(attrType) attr = (attrType)Attribute.GetCustomAttribute(targetType, typeof(attrType));
  
    if (attr == null)
    { //... }
    else
    {
      Console.Writeline(attr.IsApproved);
    }
  }

屬性。需要注意的一個重要因素是GetCustomAttribute方法旨在讀取一個且僅一個屬性。GetCustomAttribute實際上檢查是否有多個屬性匹配 - 如果沒有匹配,它將返回null,但如果有多個匹配,它將丟擲AmbiguousMatchException異常。在檢查屬性時,唯一可以安全使用GetCustomAttribute方法的情況是屬性定義宣告[AttributeUsage(AllowMultiple=false)].

從成員中檢索多個屬性

讀取成員上的多個屬性例項與讀取一個屬性沒有什麼不同;要讀取多個屬性,請使用複數形式的GetCustomAttributes方法,該方法返回一個屬性陣列。然後,你可以遍歷結果陣列並讀取值

QualityCheck[] attrArray = (QualityCheck[])Attribute.GetCustomAttributes(t, typeof(QualityCheck));

foreach (QualityCheck attr in attrArray)
{
  Console.Writeline(attr.IsApproved);
}

從多個成員中檢索單個屬性型別

如果你想做一些更復雜的事情,比如檢查類中的每個方法是否存在 QualityCheck 屬性,該怎麼辦?由於System.Reflection名稱空間的存在,我們甚至不必費力。只需將所有成員(在本例中為方法)讀入MemberInfo陣列並遍歷它們

using System.Reflection

public class ExampleClass
{
  public static void Main()
  {
    RetrieveAttributes(typeof(ExampleClass));
  }

  public void RetrieveAttributes(Type t)
  {
    MemberInfo[] methodList = t.GetMethods();
    foreach (MemberInfo m in methodList)
    {
      QualityCheck[] attrArray = (QualityCheck[])Attribute.GetCustomAttributes(m, typeof(QualityCheck));
      foreach (QualityCheck attr in attrArray)
      {
        Console.Writeline(attr.IsApproved);
      }
    }
  }
}
華夏公益教科書