《框架設計 CLR Via C# (第2版)》 - 學習筆記

**《框架設計 CLR Via C#》 (第2版)

========== ========== ==========
[作者] (美) Jeffrey Richter
[譯者] (中) 周靖 張傑良
[出版] 清華大學出版社
[版次] 2006年11月 第1版
[印次] 2007年02月 第2次 印刷
[定價] 68.00元
========== ========== ==========

【前言】

Microsoft .NET Framework 的目標不是爲構建一種特定類型的應用程序的開發人員提供一個抽象技術。相反,它的目標是爲平臺或者 Microsoft Windows 操作系統本身提供一個抽象技術。

.NET Framework 爲所有類型的應用程序提升了抽象等級。這意味着開發人員只需學習和掌握一個編程模型和一套 API (應用程序編程接口) ,不管開發人員是用它們來構建控制檯應用程序、圖形應用程序、網站,還是構建由其他應用程序使用的組件。

【第01章】

(P011)

一個方法只有在首次調用時纔會造成一定的性能損失。以後對該方法的所有調用都以本地代碼的形式全速運行,因爲不需要再次執行驗證和編譯成本地代碼。

【第02章】

(P039)

程序集是進行重用、版本控制和安全保護的一個基本單元。它允許我們將類型和資源文件劃分到單獨的文件中。

(P047)

在 Visual Studio 中新建一個 C# 項目時,會自動創建一個 AssemblyInfo.cs 文件。

(P051)

配置文件包含的是 XML 代碼,可以和一個應用程序關聯到一起,或者和機器關聯到一起。由於使用的不是註冊表設置,而是一個單獨的文件,所以文件能夠方便地進行備份,管理員也能將應用程序方便地複製到另一臺機器 —— 只需複製必要的文件,就能順便複製管理策略。

(P052)

對於可執行應用程序 (EXE) 來說,配置文件必須在應用程序的基目錄中,而且必須採用 EXE 文件的全名作爲文件名,再附加一個 .config 擴展名。

對於 Microsoft ASP.NET Web 窗體應用程序,文件必須在 Web 應用程序的虛構根目錄中,而且總是命名爲 Web.config 。除此之外,子目錄也可以包含它們自己的 Web.config 文件,而且配置設置會得以繼承。

【第03章】

(P062)

不能將一個弱命名的程序集放到 GAC 中。

建議編程人員儘量避免全局部署,儘量使用私有部署。

【第04章】

(P083)

C# 不需要任何特殊語法即可將一個對象強制轉換成它的任何基類型,因爲向基類型的轉換被認爲是一種安全的隱式轉換。

C# 要求開發人員將一個對象顯式轉換成它的任何派生類型,因爲這樣的轉型可能在運行時失敗。

(P084)

類型僞裝是造成許多安全漏洞的根源,並會破壞應用程序的穩定性和可靠性。因此,類型安全性是 CLR 的一個極其重要的目標。

在 C# 語言中進行強制類型轉換的另一種方式是使用 is 操作符。 is 操作符檢查一個對象是否兼容於指定的類型,並返回一個 Boolean 值 : true 或 false 。注意 is 操作符永遠不會拋出異常。

(P085)

假如對象引用爲 null ,那麼 is 操作符總是返回 false ,因爲無可用的對象來檢查其類型。

as 操作符的工作方式與強制類型轉換一樣,只是它永遠不會拋出一個異常 —— 相反,如果對象不能轉型,結果就是 null 。所以,正確的做法是檢查最終生成的引用是否爲 null 。如果企圖直接使用最終生成的引用,會造成一個 System.NullReferenceException 異常。

(P089)

事實上, .NET Framework 甚至根本沒有發佈一個 System.IO.dll 程序集。

【第05章】

(P098)

編譯器直接支持的任何數據類型都稱爲基元類型 (primitive type) 。基元類型直接映射到 Framework 類庫 (FCL) 中存在的類型。

(P101)

C# 總是對結果進行截斷處理,而不進行舍入。

(P104)

值類型的實例通常是在一個線程的堆棧上分配的 (雖然它們也可以嵌入一個引用類型對象中) 。

在代表值類型實例的一個變量中,並不包含一個指向實例的指針。相反,變量中包含了實例本身的字段。

由於變量已經包含實例的字段,所以在對實例的字段進行處理時,不再需要提領一個指針。

值類型的實例不受垃圾收集器的制約。因此,它們的使用緩解了託管堆上的壓力,並減少了一個應用程序在其生存期內需要進行的垃圾收集次數。

所有值類型都是密封 (sealed) 類型,目的是防止將一個值類型用作其他任何引用類型或值類型的基類型。

(P105)

在 C# 中,使用 struct 聲明的類型是值類型,使用 class 聲明的類型是引用類型。

(P106)

值類型對象有兩種表示形式 : 未裝箱 (unboxed) 形式和已裝箱 (boxed) 形式,而引用類型總是處於已裝箱形式。

引用類型的變量包含堆中的對象的地址,默認情況下,在創建一個引用類型的變量時,它被初始化爲 null ,表明引用類型變量當前並不指向一個有效的對象。

試圖使用一個 null 引用類型變量,會拋出一個 NullReferenceException 異常。

相反,值類型的變量總是包含其基礎類型的一個值,而且值類型的所有成員都初始化爲 0 。由於值類型變量不是指針,所以在訪問一個值類型時,不可能拋出一個 NullReferenceException 異常。

(P107)

將一個值類型的變量賦給另一個值類型變量時,會執行一次逐字段的複製。將引用類型的變量賦給另一個引用類型的變量時,只複製內存地址。

值類型的變量是自成一體的對象,對一個值類型變量執行的操作不可能影響另一個值類型變量。

(P109)

爲了將一個值類型轉換成一個引用類型,可以使用一個名爲 “裝箱” (boxing) 的機制。下面總結了對一個值類型的實例進行裝箱操作時在內部發生的事情 :

  1. 從託管堆中分配好內存。分配的內存量是值類型的各個字段所需要的內存量加上託管堆上的所有對象都有的兩個額外成員 (即類型對象指針和同步塊索引) 所需要的內存量;

  2. 值類型的字段複製到新分配的堆內存;

  3. 返回對象的地址。現在,這個地址是對一個對象的引用,值類型現在是一個引用類型;

(P110)

拆箱其實就是獲取一個指針的過程,該指針指向包含在一個對象中的原始值類型 (數據字段) 。事實上,指針指向的是已裝箱實例中的未裝箱部分。

在對一個對象進行拆箱操作時候,只能將其轉型爲未裝箱時的值類型。

(P115)

調用一個方法時,假如它沒有爲傳給它的一種特定的值類型準備一個重載版本,那麼最終肯定會調用接受一個 Object 參數的重載版本。將一個值類型實例作爲一個 Object 來傳遞,會造成裝箱操作的發生,從而對性能產生不利影響。

未裝箱的值類型是比引用類型更爲 “輕型” 的類型。這要歸究於以下兩個原因 :

  1. 它們不在託管堆上分配;

  2. 它們沒有堆上的每個對象都有的額外成員,也就是一個類型對象指針和一個同步塊索引;

由於未裝箱的值類型沒有同步塊索引,所以不能使用 System.Threading.Monitor 類型的各種方法 (或者使用 C# 的 lock 語句) 讓多個線程同步訪問這個實例。

(P121)

Object 的 Equals 方法實現的只是 “同一性” (identity) ,而不是 “相等性” (equality) 。

(P122)

如果想要檢查同一性 (看兩個引用是否指向同一個對象) ,那麼務必調用 ReferenceEquals ,而不應使用 C# 的 == 操作符 (除非先把兩個操作數都轉型爲 Object) ,因爲其中某個操作數的類型可能重載了 == 操作符,爲其賦予有別於 “同一性” 的其他語義。

由於 CLR 的反射機制較慢,所以在定義自己的值類型時,應該重寫 Equals 方法,並提供自己的實現,以便在用類型的實例進行值相等性比較時提高性能。

【第06章】

(P130)

定義類型時,如果沒有顯式地指定類型的可見性, C# 編譯器會將類型的可見性設爲 internal (兩者之中約束性比較強的一個) 。

(P131)

定義類型 (包括嵌套類型) 的成員時,可以指定成員的可訪問性。成員的可訪問性表明目標代碼可以合法訪問哪些成員。

(P132)

在 C# 中,如果沒有顯式地聲明成員的可訪問性,那麼,編譯器通常 (並不總是) 將成員的可訪問性默認設爲 private (可訪問性修飾符中約束性最強的一個) 。

(P133)

類不能將基類方法的可訪問性設置得更嚴格,因爲派生類的用戶通常可以強制轉換基礎類型來獲得對基類方法的訪問。

關鍵字 static 僅可以用於類,而不能用於結構 (值類型) ,這是因爲 CLR 要求值類型必須實例化,並且沒有方法停止或阻止該實例化過程。

(P134)

通過使用關鍵字 static 定義的類將導致 C# 編譯器將該類同時標記爲 abstract 和 sealed 。

(P137)

屬性和事件實際上是作爲方法實現的。

(P142)

暴露狀態極容易產生問題,它使對象的行爲無法預測,而且還公開潛在的安全漏洞。

在類的內部,始終將自己的方法、屬性和事件定義爲 private 和非虛擬的。

【第07章】

(P147)

常量總是被當作靜態成員,而不是實例成員。

(P149)

只讀字段只能在構造器方法中寫入數值 (稱之爲一次寫,即在對象首次創建時寫入數值) ,編譯器和驗證機制確保只讀字段不能被構造器外的任何其他方法寫入。需要注意的是,可以採用反射 (reflection) 來修改 readonly 字段。

(P150)

當某個字段是引用類型,並且該字段標記爲 readonly 時,它就是不可改變的引用,而不是字段所引用的對象。

【第08章】

(P151)

構造器是允許將類型實例初始化爲有效狀態的特殊方法。

創建引用類型的實例時,首先爲實例的數據字段分配內存,接着初始化對象的系統開銷字段 (類型對象指針和同步塊索引) ,最後調用類型的實例構造器設置對象的初始狀態。

創建引用類型對象時,在調用類型的實例構造器之前,爲對象分配的內存始終被清零。構造器沒有顯式賦值的所有字段保證都有一個 0 或者 null 值。

如果定義的類中沒有顯式地定義任何構造器,那麼,許多編譯器 (包括 C# 編譯器) 將定義一個默認的 (無參數的) 構造器,該構造器的實現只是調用基類的無參構造器 (parameterless constructor) 。

(P152)

如果類的修飾符爲 abstract ,那麼編譯器生成的默認構造器的可訪問性爲 protected ;否則,構造器的可訪問性爲 public 。

如果基類沒有提供無參構造器,那麼,派生類必須顯式地調用基類的構造器,否則編譯器會報錯。

如果類的修飾符爲 static (sealed 和 abstract) ,那麼,編譯器就根本不會在類的定義中生成一個默認的構造器。

一個類型可以定義多個實例構造器。每個構造器都必須擁有一個不同的簽名,而且每個構造器可以擁有不同的可訪問性。

對於可驗證的代碼 (verifiable code) ,類的實例構造器在訪問從基類繼承的任何字段之前,必須調用其基類的構造器。

最終,類的實例構造器將調用基類 System.Object 的公有無參構造器。該構造器不執行任何代碼,只是簡單地返回,因爲基類 System.Object 沒有定義實例數據字段,因此它的構造器沒有代碼可以執行。

C# 語言提供了一個簡單的語法,允許在構建類型實例的過程中初始化引用類型中定義的字段。

C# 編譯器提供了一個方便的語法來內聯初始化實例字段,並且將這個語法轉換成構造器方法中的代碼以執行初始化。

(P154)

CLR 確實允許在值類型上定義構造器,但是執行值類型上定義的構造器的惟一方法是編寫代碼顯式地調用這些構造器。

(P155)

值類型的實例構造器只有在被顯式調用時纔會執行。

(P156)

除了實例構造器外, CLR 還支持類型構造器 (type constructor) ,也稱爲靜態構造器 (static constructor) 、類構造器 (class constructor) 或者類型初始化器 (type initializer) 。

和實例構造器用來設置類型的實例的初始狀態一樣,類型構造器用來設置類型的初始狀態。

默認情況下,類型不在類型內部定義類型構造器。如果類型定義了類型構造器,那麼類型構造器的數量不能超過一個。另外,類型構造器永遠沒有參數。

(P157)

定義類型構造器的方法類似於定義無參實例構造器,惟一的區別在於必須將類型構造器標記爲 static 。

類型構造器通常也應是私有的, C# 會自動地將類型構造器標記爲 private 。

因爲 CLR 保證每個應用程序域的類型構造器只執行一次,而且是線程安全的,所以最後合在類型構造器中初始化類型的單實例對象。

(P158)

如果類型構造器拋出一個未處理的異常, CLR 就會認爲類型不可使用。試圖訪問該類型的任何字段或者方法都將導致 CLR 拋出一個 System.TypeInitializationException 異常。

類型構造器中的代碼只能訪問類型的靜態字段,並且它的常規用途就是初始化這些靜態字段。就像對待實例字段一樣, C# 提供了一個簡單的語法來初始化類型的靜態字段。

雖然 C# 不允許值類型使用內聯字段初始化語法初始化實例字段,但允許用它來初始化靜態字段。

(P162)

CLR 規範將操作符重載方法指定爲 public 和 static 方法。

(P166)

在關鍵字 implicit 或關鍵字 explicit 之後,可以指定關鍵字 operator 向編譯器表明該方法是一個轉換操作符方法。在關鍵字 operator 後面,還需要指定對象要強制轉換成什麼類型,而在圓括號中,需要指定要進行強制轉換的對象的類型。

(P167)

默認情況下, CLR 假定所有方法參數是按值傳遞的。

在方法中,必須知道傳遞的每個參數是引用類型還是值類型,因爲編寫的用來處理參數的代碼會因此存在明顯的差異。

從 CLR 的角度看,關鍵字 out 和關鍵字 ref 是等效的,這就是說,無論使用哪個關鍵字,都會生成相同的元數據和 IL 代碼。

(P168)

從 IL 和 CLR 的角度看, out 和 ref 功能相同 —— 它們都生成一個所傳遞實例的指針;這兩個關鍵字的區別在於需要編譯器進一步保證代碼的正確性。

(P170)

按引用傳遞給方法的變量的類型必須與方法簽名中聲明的類型相同。

(P172)

關鍵字 params 是應用於方法簽名的最後一個參數。

關鍵字 params 向編譯器表明將 System.ParamArrayAttribute 實例的自定義屬性應用到參數上。

(P173)

只有方法的最後一個參數纔可以標記關鍵字 params (ParamArrayAttribute) 。該參數必須標識一個一維數組,但類型不限。對方法的最後一個參數傳遞 null 或者 0 個條目的數組的引用都是合法的。

【第09章】

(P178)

CLR 支持靜態屬性、實例屬性、抽象屬性和虛擬屬性。

(P181)

在 C# 中,使用與數組類似的語法對外提供有參屬性 (索引器) 。換句話說,我們可以將索引器看作 C# 開發人員重載運算符 [] 的一種方式。

(P182)

所有的索引器必須至少擁有一個參數,也可以擁有多個參數。這些參數以及返回類型可以是任意的數據類型 (除了 void) 。

和無參屬性的 set 訪問器方法相似,索引器的 set 訪問器方法同樣也包含了一個隱藏的參數,在 C# 中稱之爲 value 。該參數表明 “索引元素 (indexed element)” 期望的新值。

CLR 本身並不區別無參屬性和有參屬性,對 CLR 來講,每個屬性只是定義在類型中的一對方法和一塊元數據。

(P183)

在 C# 中,每個類型都可以定義多個索引器,只要索引器的參數集不同即可。

(P185)

當定義有訪問器方法的屬性擁有不同的可訪問性時,C# 語法要求屬性本身必須聲明爲最低約束性的可訪問性,而且約束性較強的可訪問性只可以應用於一個訪問器方法。

【第10章】

(P186)

公共語言運行庫 (Common Language Runtime , CLR) 的事件模型建立在委託 (delegate) 這一機制之上。委託是一種類型安全的調用回調方法 (callback method) 的方式。回調方法意味着哪個對象接收對象所訂閱事件的通知。

(P187)

按照約定,所有傳遞給事件處理程序的用於存放事件信息的類都應該繼承自 System.EventArgs ,並且類的名稱應該以 EventArgs 結束。

(P188)

定義一個不需要傳遞任何額外信息的事件時,可以直接使用 EventArgs.Empty ,不用構建一個新的 EventArgs 對象。

事件成員使用 C# 關鍵字 event 定義。每個事件成員都有一個給定的可訪問性 (通常都爲 public ,以便於其他代碼也可以訪問這個事件成員) 、一個表示即將被調用方法的原型的委託類型以及一個名稱 (可以是任意有效的標識符) 。

(P189)

事件模式要求所有的事件處理程序的返回類型都爲 void 。

按照約定,類應定義一個受保護的虛方法,當引發事件時,這個類及其派生類中的代碼可以調用這個虛方法。

(P190)

設計一個對外提供事件的類型 :

第一步 : 定義一個類型用於存放所有需要發送給事件通知接收者的附加信息;

第二步 : 定義事件成員;

第三步 : 定義一個負責引發事件的方法,來通知已訂閱事件的對象事件已經發生;

第四步 : 定義一個方法,將輸入轉化爲期望事件;

(P193)

C# 要求代碼使用 += 和 -= 操作符在鏈表上添加和移除委託。

(P196)

事件必須同時擁有 add 和 remove 訪問器方法。

【第11章】

(P201)

在 .NET Framework 中,字符總是表示成 16 位 Unicode 代碼值,這簡化了全球應用程序的開發。一個字符表示成 System.Char 結構 (一個值類型) 的一個實例。

(P202)

可以使用三種技術實現各種數值類型與 Char 實例的相互轉換 : 轉型 (強制類型轉換) 、使用 Convert 類型、 使用 IConvertible 接口。

(P204)

System.String 是任何一個應用程序使用得最多的類型之一。

一個 String 代表一個不可變的順序字符集。

String 類型直接派生自 Object ,這使其成爲一個引用類型。因此,String 對象 (它的字符數組) 總是存在於堆上,而不在線程的堆棧上。

在 C# 中,不能通過 new 操作符在一個直接量字符串的基礎上構造一個 String 對象。

公共語言運行庫 (CLR) 事實上採取一種特殊的方式來構造直接量 String 對象。

(P205)

在字符串之前添加 @ 符號,使編譯器知道字符串是一個逐字字符串。

(P206)

要想高效地執行大量字符串操作,請使用 StringBuilder 類。

String 類必須是密封類 (sealed) 。

(P217)

用 StringBuilder 對象構造好字符串之後,爲了將 StringBuilder 的字符數組 “轉換” 成一個 String ,只需調用 StringBuilder 的 ToString 方法。在內部,該方法只是返回對 StringBuilder 內部維護的字符串字段的一個引用。這使 StringBuilder 的 ToString 方法可以非常快地執行,因爲字符數組不需要複製。

(P218)

數組的動態擴容會損害性能。要避免這個危害,需要設置一個合適的初始容量。

StringBuilder 只在以下兩種情況下分配一個新的對象 :

  1. 試圖動態構造一個字符串,它的長度超過了事先設置的 “容量” ;

  2. 試圖在調用 StringBuilder 的 ToString 方法之後修改數組;

(P220)

System.Object 定義了一個 public 、 virtual 、無參數的 ToString 方法,所以在任何類型的一個實例上都能調用這個方法。

ToString 的 System.Object 實現的是返回對象所屬類型的全名。

(P222)

IFormatProvider 接口的基本思路是 : 假如一個類型實現了該接口,就認爲類型的一個實例能夠提供依賴於語言文化的格式化信息,而與調用線程關聯的語言文化應被忽略。

(P224)

在內部, Format 方法會調用每個對象的 ToString 方法來獲取對象的一個字符串表示。

採取在大括號中指定格式信息的方式,可以對一個對象的格式化進行更多的控制。

(P227)

能解析一個字符串的任何類型都提供了一個名爲 Parse 的 public static 方法。該方法獲取一個 String 對象,並返回類型的一個實例。從某個角度來說, Parse 相當於一個 factory 方法。

(P229)

在 CLR 中,所有字符都是以 16 位 Unicode 代碼值的形式來表示的,而且所有字符串都由 16 位 Unicode 代碼值構成。

(P231)

Encoding 是一個抽象基類,它提供了幾個靜態只讀屬性,每個屬性都返回從 Encoding 派生的一個類的實例。

(P232)

一旦獲得從 Encoding 派生的一個對象之後,就可以調用 GetBytes 方法,將一個字符串或者一個字符數組轉換成一個字節數組 (該方法有幾個重載版本) 。要將字節數組轉換成字符數組,需要調用 GetChars 方法或者更有用的 GetString 方法 (這兩個方法都有幾個重載版本) 。

【第12章】

(P240)

枚舉類型不能定義任何方法、屬性或事件。

枚舉類型只是一個在其中定義一系列常量字段和實例字段的結構。

【第13章】

(P247)

所有數組類型都隱式地從 System.Array 抽象類派生,後者又派生自 System.Object 。這意味着數組始終爲引用類型,是在託管堆上進行分配的,應用程序的變量或字段中包含的是對數組的引用,而不是對數組本身所含元素的引用。

【第14章】

(P262)

在 CLR 中,類始終繼承自一個而且只有一個類 (最終肯定繼承自 Object) 。

CLR 還允許開發人員定義一個接口,接口實際只是爲一組方法簽名指定一個名稱的方式。

類繼承的一個重要特性是,在希望出現基類型實例的任何地方,都可以替換成派生類的實例。

接口繼承允許在希望出現已命名接口類型的實例的任何地方,都可以替換成實現接口的一個類型的實現。

接口是一組已命名的方法簽名。

接口還可以定義事件、無參數的屬性和參數化的屬性 (在 C# 中是索引器) ,因爲無論怎樣,所有這些在本質上都是方法。

一個接口不能定義任何構造器方法。

接口也不允許定義任何實例字段。

(P263)

在定義接口類型時,可以隨心所欲地指定 可視性 / 可訪問性 (public , protected , internal 等) 。

(P264)

C# 編譯器要求將實現了接口的方法標記爲 public 。 CLR 要求將接口方法標記爲 virtual 。

(P267)

注意,用 C# 定義一個顯式接口方法時,不允許指定訪問性 (比如公共或私有) 。但是,在編譯器生成方法的元數據時,其訪問性被設置爲私有,目的是防止使用類實例的任何代碼直接調用接口方法。要想調用接口方法,只能通過一個接口類型的變量來進行。

泛型接口提供了出色的編譯時類型安全性。

(P268)

泛型接口的第二個好處是在操作值類型時,不需要太多裝箱操作。

有的泛型接口繼承了非泛型版本,所以我們所寫的類必須實現接口的泛型和非泛型版本。

泛型接口的第三個好處是,類可以實現同一個接口若干次,只要使用不同的類型參數。

【第15章】

(P280)

對於一個通過委託來調用另一個類型的私有成員的類型而言,這樣做不會損害其安全性或可訪問性,只要這個委託對象是由具有足夠安全性或可訪問性的代碼來創建的。

將一個方法綁定到一個委託時, C# 和 CLR 都允許引用類型的協變 (covariance) 和反協變 (contra-variance) 。協變指的是一個方法能返回從委託的返回類型派生的一個類型。反協變指的是一個方法的參數類型可以是委託的參數類型的基類。

注意,協變與反協變只能用於引用類型,不能用於值類型或 void 。

(P282)

所有委託類型都繼承自 MulticastDelegate 。

System.MulticastDelegate 類繼承自 System.Delegate ,後者本身繼承自 System.Object 。

委託類可以在一個類型內部 (即嵌套在另一個類型內) 或在全局範圍內定義。簡單地說,因爲委託是類,在可以定義類的任何地方,都可以定義委託。

(P285)

鏈式委託指的是由一系列委託對象組成的集合,它允許調用集合中各個委託所表示的所有方法。

Delegate 類的公共靜態方法 Combine 用於添加一個委託到委託鏈。

(P288)

C# 編譯器自動爲委託類型的實例提供了運算符 += 和 -= 重載。這些運算符分別調用 Delegate.Combine 和 Delegate.Remove 。

(P292)

當 C# 編譯器看到期望收到委託對象引用的地方使用了 delegate 關鍵字,就會自動在類中定義一個新的私有方法。這個新方法叫做匿名方法 (anonymous method) ,因爲編譯器自動爲我們創建了方法名,而且通常情況下,我們並不需要知道這個名稱。

(P294)

如果回調代碼引用了任何一個參數,在 delegate 關鍵字後面必須包含括號、參數類型和變量名稱。返回類型仍然可以從委託的類型推斷出來,而且如果返回類型不爲 void ,還必須在內聯的回調代碼內部包含一個 return 語句。

【第16章】

(P301)

定義一個泛型類型或方法時,它爲類型指定的任何變量 (比如 T) 都稱爲 “類型參數” (type parameter) 。 T 是一個變量名,在源代碼中能夠使用一個數據類型的任何位置,都能使用 T 。

由於在能夠指定一個數據類型的任何地方使用 T 變量,所以在方法內部定義一個局部變量時,或者在一個類型中定義字段時,也可以使用 T 。

使用一個泛型類型或者方法時,指定的具體數據類型被稱爲 “類型實參” (type argument) 。

(P307)

具有泛型類型參數的一個類型被稱爲 “開放式類型” (open type) , CLR 禁止構造開放式類型的任何實例。

(P311)

沒有泛型接口,每次試圖使用一個非泛型接口來操縱一個值類型時,都會進行裝箱,而且會丟失編譯時的類型安全性。這會嚴重限制泛型類型的應用。

(P312)

CLR 支持泛型委託,目的是保證任何類型的對象都能以一種類型安全的方式傳給一個回調方法。

(P313)

定義一個泛型引用類型、值類型或者接口時,這些類型中定義的任何方法都可以引用類型指定的一個類型參數。類型參數可以作爲方法的參數,作爲方法的返回值,或者作爲方法內部定義的一個局部變量來使用。然而, CLR 還允許一個方法指定它獨有的類型參數。這些類型參數可用於參數、返回值或者局部變量。

(P317)

類型參數可以指定零個或者一個主要約束 (primary constraint) 。主要約束可以是一個引用類型,它標識了一個沒有密封的類。

指定一個引用類型約束時,相當於向編譯器承諾 : 一個指定的類型實參要麼是與約束類型相同的類型,要麼是從約束類型派生的一個類型。

(P318)

class 約束向編譯器承諾一個指定的類型實參是引用類型。任何類類型、接口類型、委託類型或者數組類型都滿足這個約束。

struct 約束向編譯器承諾一個指定的類型實參是值類型。包括枚舉在內的任何值類型都滿足這個約束。

一個類型參數可以指定零個或者多個次要約束,次要約束代表的是一個接口約束。指定一個接口類型約束時,是向編譯器承諾一個指定的類型實參是實現了接口的一個類型。由於能指定多個接口約束,所以爲類型實參指定的類型必須實現所有接口約束 (以及所有主要約束,如果指定了的話) 。

(P319)

一個類型參數可以指定零個或者一個構造器約束。指定構造器約束相當於向編譯器承諾 : 一個指定的類型實參是實現了一個 public 無參數構造器的一個非抽象類型。注意,如果同時指定了構造器約束和 struct 約束, C# 編譯器會認爲這是一個錯誤,因爲這是多餘的;所有值類型都隱式提供了一個 public 無參數構造器。

(P320)

將一個泛型類型變量轉型爲另一個類型是非法的,除非將其轉換爲與一個約束兼容的類型。

將泛型類型變量設爲 null 是非法的,除非將泛型類型約束成一個引用類型。

(P321)

Microsoft 的 C# 團隊認爲有必要允許開發人員將一個變量設爲一個默認值。所以, C# 編譯器允許使用 default 關鍵字來實現這個操作。

【第17章】

(P323)

聲明式編程是指使用數據而不是寫源代碼來指示 應用程序 / 組件去做某事。

“自定義特性” 是一種特殊的技術,它允許和命令式編程 (C# 源代碼) 配合使用聲明式編程。這種組合式編程爲編程人員提供了極大的靈活性,並允許以一種非常簡潔的方式表達開發人員的編碼意圖。

(P326)

爲了保持與 “公共語言規範” (CLS) 相容,自定義特性類必須直接或間接地從公共抽象類 System.Attribute 派生。

可以將多個特性應用於單個目標元素。

將多個特性應用於單個目標元素時,注意特性的順序是無關緊要的。

在 C# 中,可以將每個特性都封閉到一對方括號中,也可以在一對方括號中封閉多個以逗號分隔的特性。

假如特性類的構造器不獲取參數,圓括號就是可有可無的。

【第18章】

(P344)

C# 提供了一個所謂的 “空結合操作符 (null-coalescing operator)” ,即 “??” 操作符,它要獲取兩個操作數。假如左邊的操作數不爲 null ,就返回這個操作數的值。如果左邊的操作數爲 null ,就返回右邊的操作數的值。利用空結合操作符,可以方便地設置變量的默認值。

空結合操作符的一個妙處在於,它既能用於引用類型,也能用於可空值類型。

【第19章】

(P352)

try 塊中包含的代碼通常要求執行公共資源清理操作,或者要求執行異常恢復操作,或者兩種操作都要求。

資源清理代碼應放在一個單獨的 finally 塊中。

try 塊同樣可以包含可能會拋出異常的代碼。

異常恢復代碼應該放在一個或多個 catch 塊中。

我們應該爲應用程序可以從中恢復的每一種異常都創建一個 catch 塊。

一個 try 塊必須至少有一個 catch 塊或者 finally 塊與其關聯,單獨一個 try 塊是沒有任何意義的,而且 C# 也會阻止我們這樣做。

catch 塊中包含的是響應異常時需要執行的代碼。一個 try 塊可以有 0 個或者多個 catch 塊與其關聯。如果 try 塊中的代碼沒有拋出異常,那麼 CLR 永遠不會執行與該 try 塊相關聯的所有 catch 塊中的代碼。這時,線程就會跳出所有的 catch 塊,直接執行 finally 塊中的代碼 (如果存在的話)。 在 finally 塊中的代碼執行完畢後,線程就會繼續執行 finally 塊後面的語句。

(P354)

一個 try 塊並不要求必須有一個 finally 塊與其關聯,有時候 try 塊中的代碼並不需要任何清理工作。但是,如果確實有 finally 塊,那麼它必須出現在所有的 catch 塊之後。而且,一個 try 塊最多只能有一個與其關聯的 finally 塊。

當線程執行完 finally 塊中包含的代碼後,線程立即開始執行緊跟在 finally 塊後的語句。記住, finally 塊中的代碼是清理代碼,該代碼只有在 try 塊中發起的操作需要進行清理時才被執行。應該避免將可能拋出異常的代碼放在 finally 塊中。

(P376)

System.Exception 類型提供了一個公共的只讀屬性 StackTrace 。 catch 塊可以讀取該屬性來獲取異常的堆棧跟蹤,異常的堆棧跟蹤指出了異常經過的路徑中所發生的事件,它對於我們檢測異常原因、進而修正代碼來說非常有用。

【第20章】

(P397)

Finalize 方法在垃圾收集結束時被調用,有 5 種事件會導致一個對象的 Finalize 方法被調用 :

  1. 第 0 代對象充滿;

  2. 代碼顯式地調用 System.GC 的靜態方法 Collect ;

  3. Windows 報告內存不足;

  4. CLR 卸載應用程序域;

  5. CLR 被關閉;

(P399)

一個對象要成爲可終結的對象,那麼在它的類型及其基礎類型中 (除 Object 之外) ,必須至少有一個類型重寫了 Object 的 Finalize 。

(P401)

Finalize 方法非常有用,因爲它可以確保託管對象在釋放內存的同時不會泄露本地資源。但是 Finalize 方法的問題在於我們並不能確定它會在何時被調用,而且由於它並不是一個公共方法,我們也不能顯式地調用它。

要提供確定釋放或者關閉對象的能力,一個類型通常要實現一種釋放模式 (dispose pattern) 。釋放模式定義了開發人員在實現類型的顯式資源清理功能時要遵循的一些約定。如果一個類型實現了釋放模式,那麼使用該類型的開發人員將能夠知道當對象不再被使用時如何顯式地釋放掉它所佔用的資源。

(P406)

記住 Close 方法並不是釋放模式正式定義的一部分,有些類型提供了 Close 方法,而有些類型則不會提供 Close 方法。

(P409)

FileStream 類型只支持字節的讀寫操作。如果我們希望支持字符或者字符串的讀寫操作,可以使用 System.IO.BinaryWriter 類型。

注意 StreamWriter 的構造器接受一個 Stream 對象的引用作爲參數,允許 FileStream 對象的引用作爲參數進行傳遞。 BinaryWriter 對象內部會保存 Stream 對象的引用。當我們向一個 StreamWriter 對象寫入數據時,它會將數據緩存在自己的內存緩衝區中。當 StreamWriter 對象的內存緩衝區充滿時, StreamWriter 對象纔會將數據寫入 Stream 對象。

【第21章】

(P438)

CLR 初始化時創建的第一個應用程序域稱爲默認程序域 (default AppDomain) ,該應用程序域只有在進程終止時纔會被銷燬。

(P443)

在 Windows 中,線程通常在進程的上下文中創建,而且線程的生存期與該進程的生存期相同。但是線程和應用程序域之間沒有一對一的關係。

【第22章】

(P466)

GetType 方法在運行時返回對象的類型 (晚綁定) ;

typeof 方法返回指定類的類型 (早綁定) ;

(P476)

調用 GetMembers 方法返回的數組中的每一個元素都是前面反射類型層次結構中的一個具體類型 (除非指定了 BindingFlags.DeclaredOnly 標記) 。儘管 Type 的 GetMembers 方法可以返回所有類型的成員,但 Type 還提供有一些方法可以返回特定的成員類型,例如 GetNestedTypes , GetFields , GetConstructors , GetMethods , GetProperties 以及 GetEvents 方法。這些方法返回的都是一個數組,其元素分別爲下述對象的引用 : Type 對象、 FieldInfo 對象、 ConstructorInfo 對象、 MethodInfo 對象、 PropertyInfo 對象以及 EventInfo 對象。

【第24章】

(P511)

除了縮短並簡化了代碼外, lock 語句還可以保證對 Monitor.Exit 方法的調用,因此確保了即使在 lock 塊內發生異常時也可以釋放同步塊。

(P535)

永遠不要爲 Monitor.Enter 方法或者 C# 的 lock 語句傳遞值類型的變量。因爲未裝箱值類型實例沒有同步塊索引成員,因此它們不能用於同步。
**

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