主頁 >  其他 > 《果殼中的C# C# 5.0 權威指南》 - 學習筆記

《果殼中的C# C# 5.0 權威指南》 - 學習筆記

2020-09-16 21:47:24 其他

《果殼中的C# C# 5.0 權威指南》

========== ========== ==========
[作者] (美) Joseph Albahari (美) Ben Albahari
[譯者] (中) 陳昇 管學理 曾少寧 楊慶川
[出版] 中國水利水電出版社
[版次] 2013年08月 第1版
[印次] 2013年08月 第1次 印刷
[定價] 118.00元
========== ========== ==========

【前言】

C# 5.0 是微軟旗艦編程語言的第4次重大升級,

C# 5.0 及相關 Framework 的新特性已經被標注清楚,因此也可以將本書作為 C# 4.0 參考書使用,

【第01章】

(P001)

C# 在面向物件方面的特性包括:

1. 統一的型別系統 —— C# 中的基礎構建塊是一種被稱為型別的資料與函式的封裝單元,C# 有一個統一的型別系統,其中所有型別最終都共享一個公共的基類,這意味著所有的型別,不管它們是表示業務物件,或者像數字等基本型別,都共享相同的基本功能集;

2. 類與介面 —— 在純粹的的面向物件泛型中,唯一的型別就是類,但是 C# 中還有其他幾種型別,其中一種是介面,介面與類相似,但它只是某種型別的定義,而不是實作,在需要用多繼承時,它是非常有用的;

3. 方法、屬性與事件 —— 在純粹的面向物件泛型中,所有函式都是方法,在 C# 中,方法只是一種函式成員,也包含一些屬性和事件以及其他組成部分,屬性是封裝了一部分物件狀態的函式成員,事件是簡化物件狀態變化處理的函式成員;

C# 首先是一種型別安全的語言,這意味著型別只能夠通過它們定義的協議進行互動,從而保證每一種型別的內部一致性,

C# 支持靜態型別化,這意味著這種語言會在編譯時執行靜態型別安全性檢查,

(P002)

靜態型別化能夠在程式運行之前去除大量的錯誤,

C# 允許部分代碼通過新的 dynamic 關鍵字來動態指定型別,然而,C# 在大多數情況下仍然是一種靜態型別化的語言,

C# 之所以被稱為一種強型別語言,是因為它的型別規則是非常嚴格的,

C# 依靠運行時環境來執行自動的記憶體管理,

C# 并沒有去除指標 : 它只是使大多數編程任務不需要使用指標,對于性能至關重要的熱點和互操作性方面,還是可以使用指標,但是只允許在顯式標記為不安全的代碼塊中使用,

C# 依賴于一個運行時環境,它包括許多特性,如自動記憶體管理和例外處理,

(P003)

.NET Framework 由名為 Common Language Runtime (CLR) 的運行時環境和大量的程式庫組成,這些程式庫由核心庫和應用庫組成,

CLR 是執行托管代碼的運行時環境,C# 是幾種將源代碼編譯為托管語言之一,托管代碼會被打包成程式集,它可以是可執行檔案或程式庫的形式,包括型別資訊或元資料,

托管代碼用 Intermediate Language 或 IL 表示,

Red Gate 的 .Net Reflector 是一個重要的分析程式集內容的工具 (可以將它作為反編譯器使用) ,

CLR 是無數運行時服務的主機,這些服務包括記憶體管理、程式庫加載和安全性服務,

CLR 是與語言無關的,它允許開發人員用多種語言開發應用程式,

(P004)

.NET Framework 由只支持基于所有 Windows 平臺或 Web 的應用程式的程式庫組成,

C# 5.0 還實作了 Windows Runtime (WinRT) 庫的互操作,

WinRT 是一個擴展介面和運行時環境,它可以用面向物件和與語言無關的方式訪問庫,Windows 8 帶有這個運行時庫,屬于 Microsoft 組件物件模型或 COM 的擴展版本,

Windows 8 帶有一組非托管 WinRT 庫,它是通過 Microsoft 應用商店交付的支持觸摸屏的 Metro 風格應用程式框架,作為 WinRT ,這些程式庫不僅可以通過 C# 和 VB 訪問,也可以通過 C++ 和 JavaScript 訪問,

WinRT 與普通 COM 的區別是,WinRT 的程式庫支持多種語言,包括 C# 、 VB 、 C++ 和 JavaScript,所以每一種語言 (幾乎) 都將 WinRT 型別視為自己的專屬型別,

(P005)

C# 5.0 兩個較大的新特性是通過兩個關鍵字 (async 和 await) 支持異步功能 (asynchronous function),

C# 4.0 增加的新特性有 : 動態系結、可選引數和命名引數、用泛型介面和代理實作型別變化、改進 COM 互操作性,

C# 3.0 增加的這些特性主要集中在語言集成查詢功能上 (Language Integrated Query,簡稱 LINQ) ,

C# 3.0 中用于支持 LINQ 的新特性還包括隱式型別化區域變數 (Var) 、匿名型別、物件構造器、 Lambda 運算式、擴展方法、查詢運算式和運算式樹,

(P006)

C# 3.0 也增加了自動化和區域方法,

【第02章】

(P007)

在 C# 中陳述句按順序執行,每個陳述句都以分號 (;) 結尾,

C# 陳述句按順序執行,以分號 (;) 結尾,

(P008)

方法是執行一系列陳述句的行為,這些陳述句叫做陳述句塊,陳述句塊由一對大括號中的 0 個或多個陳述句組成,

撰寫可呼叫低級函式的高級函式可以簡化程式,

方法可以通過引數來接收呼叫者輸入的資料,并通過回傳型別給呼叫者回傳輸出資料,

C# 把 Main 方法作為程式的默認執行入口, Main 方法也可以回傳一個整數 (而不是 void) ,從而為程式執行的環境回傳一個值, Main 方法也可以接受一個字串陣列作為引數 (陣列中包含可傳遞給可執行內容的任何引數) ,

陣列代表某種特定型別,固定數量的元素的集合,陣列由元素型別和它后面的方括號指定,

類由函式成員和資料成員組成,形成面向物件的構建塊,

(P009)

在程式的最外層,型別被組織到命名空間中,

.NET Framework 的組織方式為嵌套的命名空間,

using 指令僅僅是為了方便,也可以用 “命名空間 + 型別名” 這種完全限定名稱來參考某種型別,

C# 編譯器把一系列 .cs 擴展名的源代碼檔案編譯成程式集,

程式集是 .NET 中的最小打包和部署單元,

一個程式集可以是一個應用程式,或者是一個庫,

一個普通的控制臺程式或 Windows 應用程式是一個 .exe 檔案,包含一個 Main 方法,

一個庫是一個 .dll 檔案,它相當于一個沒有入口的 .exe 檔案,

庫是用來被應用程式或其他的庫呼叫 (參考) 的,

.NET Framework 就是一組庫,

C# 編譯器名稱是 csc.exe,可以使用像 Visual Studio 這樣的 IDE 編譯 C# 程式,也可以在命令列中手動呼叫 csc 命令編譯 C# 程式,

(P010)

識別符號是程式員為類、方法、變數等選擇的名字,

識別符號必須是一個完整的詞、它是由字母和下劃線開頭的 Unicode 字符組成的,

C# 識別符號是區分大小寫的,

通常約定引數、區域變數和私有變數欄位應該由小寫字母開頭,而其他型別的識別符號則應該由大寫字母開頭,

關鍵字是編譯器保留的名稱,不能把它們用作識別符號,

如果用關鍵字作為識別符號,可以在關鍵字前面加上 @ 前綴,

@ 并不是識別符號的一部分,

@ 前綴在呼叫其他有不同關鍵字的 .NET 語言撰寫的庫時非常有用,

(P011)

點號 (.) 表示某個物件的成員 (或數字的小數點),

括號在宣告或呼叫方法時使用,空括號在方法沒有引數時使用,

等號則用于賦值操作,

C# 提供了兩種方式的注釋 : 單行注釋和多行注釋,

單行注釋由雙斜線開始,到本行結束為止,

多行注釋由 /* 開始,由 */ 結束,

變數代表它的值可以改變,而常量則表示它的值不可以更改,

(P012)

C# 中所有值都是一種型別的實體,一個值或一個變數所包含的一組可能值均由其型別決定,

預定義型別是指那些由編譯器特別支持的型別,

預定義型別 bool 只有兩種值 : true 和 false , bool 型別通常與 if 陳述句一起用于條件分支,

在 C# 中,預定義型別 (也稱為內建型別) 被當做 C# 關鍵字,在 .NET Framework 中的 System 命名空間下包含了很多并不是預定義型別的重要型別,

正如我們能使用簡單函式來構建復雜函式一樣,也可以使用基本型別來構建復雜型別,

(P013)

型別包含資料成員和函式成員,

C# 的一個優點就是預定義型別和自定義型別只有很少的不同,

實體化某種型別即可創建資料,

預定義型別可以簡單地通過字面值進行實體化,

new 運算子用于創建自定義型別的實體,

使用 new 運算子后會立刻實體化一個物件,物件的構造方法會在初始化時被呼叫,

構造方法像方法一樣被定義,不同的是方法名和回傳型別簡化成它所屬的型別名,

由型別的實體操作的資料成員和函式成員被稱為實體成員,

在默認情況下,成員就是實體成員,

(P014)

那些不是由型別的實體操作而是由型別本身操作的資料成員和函式成員必須標記為 static ,

public 關鍵字將成員公開給其他類,

把成員標記為 public 就是在說 : “這就是我想讓其他型別看到的,其他的都是我自己私有的” ,

用面向物件語言,我們稱之為公有 (public) 成員封裝了類中的私有 (private) 成員,

在 C# 中,兼容型別的實體可以相互轉換,

轉換始侄訓根據一個已經存在的值創建一個新的值,

轉換可以是隱式或顯式,

隱式轉換自動發生,而顯式轉換需要 cast 關鍵字,

long 容量是 int 的兩倍,

(P015)

隱式轉換只有在下列條件都滿足時才被允許 :

1. 編譯器能保證轉換總是成功;

2. 沒有資訊在轉換程序中丟失;

只有在滿足下列條件時才需要顯式轉換:

1. 編譯器不能保證轉換總是能成功;

2. 資訊在轉換程序中有可能丟失;

C# 還支持參考轉換,裝箱轉換和自定義轉換,

對于自定義轉換,編譯器并沒有強制遵守上面的規則,所以設計不好的型別有可能在轉換時出現預想不到的結果,

所有 C# 型別可以分成以下幾類 : 值型別、參考型別、泛型型別、指標型別,

值型別包含大多數內建型別 (具體包括所有的數值型別、 char 型別和 bool 型別) 以及自定義 struct 型別和 enum 型別,

參考型別包括所有的類、資料、委托和介面型別,

值型別和參考型別最根本的不同是它們在記憶體中的處理方式,

值型別變數或常量的內容僅僅是一個值,

可以通過 struct 關鍵字定義一個自定義值型別,

對值型別實體的賦值操作總是會復制這些實體,

將一個非常大的 long 轉換成 double 型別時,有可能造成精度丟失,

(P016)

參考型別比值型別復雜,它由兩部分組成 : 物件和物件的參考,

參考型別變數或常量的內容是對一個包含值的物件的參考,

(P017)

一個參考可以賦值為字面值 null,這表示它不指向任何物件;

相對的,值型別通常不能有 null 值;

C# 中也有一種代表型別值為 null 的結構,叫做可空 (nullable) 型別,

(P018)

值型別實體正好占用需要存盤其欄位的記憶體,

從技術上說,CLR 用整數倍欄位的大小來分配記憶體地址,

參考型別要求為參考和物件單獨分配存盤空間,

物件占用了和欄位一樣的位元組數,再加上額外的管理開銷,

每一個物件的參考都需要額外的 4 或 8 位元組,這取決于 .NET 運行時是運行在 32 位平臺還是 64 位平臺上,

C# 中的預定義型別又稱框架型別,它們都在 System 命名空間下,

在 CLR 中,除了 decimal 之外的一系列預定義值型別被認為是基本型別,之所以將其稱為基本型別,是因為它們在編譯過的代碼中被指令直接支持,因此它們通常被翻譯成底層處理器直接支持的指令,

(P019)

System.IntPtr 和 System.UIntPtr 型別也是基本型別,

在整數型別中,int 和 long 是最基本的型別, C# 和運行時都支持它們,其他的整數型別通常用于實作互操作性或存盤空間使用效率非常重要的情況,

在實數型別中,float 和 double 被稱為浮點型別,通常用于科學計算,

decimal 型別通常用于要求10位精度以上的數值計算和高精度的金融計算,

整型字面值可使用小數或十六進制小數標記,十六進制小數用 0x 前綴表示,

實數字面值可使用小數和指數標記,

從技術上說,decimal 也是一種浮點型別,但是在 C# 語言規范中通常不將其認為是浮點型別,

(P020)

默認情況下,編譯器認為數值字面值或者是 double 型別或者是整數型別 :

1. 如果這個字面值包含小數點或指數符號 (E),那么它被認為是 double ;

2. 否則,這個字面值的型別就是下列能滿足這個字面值的第一個型別 : int 、 uint 、 long 和 ulong ;

數值后綴顯式地定義了一個字面值的型別,后綴可以是下列小寫或大寫字母 : F (float) 、 D (double) 、 M (decimal) 、 U (uint) 、 L (long) 、 UL (ulong) ,

后綴 U 、 L 和 UL 很少需要,因為 uint 、 long 和 ulong 總是可以表示 int 或從 int 隱式轉換過來的型別,

從技術上講,后綴 D 是多余的,因為所有帶小數點的字面值都被認為是 double 型別,總是可以給一個數字型別加上小數點,

后綴 F 和 M 是最有用的,它在指定 float 或 decimal 字面值時使用,

double 是無法隱式轉換成 float 的,同樣的規則也適用于 decimal 字面值,

整型轉換在目標型別能表示源型別所有可能的值時是隱式轉換,否則需要顯式轉換,

(P021)

float 能隱式轉換成 double ,因為 double 能表示所有可能的 float 的值,反過來則必須是顯式轉換,

所有的整數型別可以隱式轉換成浮點數,反過來則必須是顯式轉換,

將浮點數轉換成整數時,小數點后的數值將被截去,而不會四舍五入,

靜態類 System.Convert 提供了在不同值型別之間轉換的四舍五入方法,

把一個大的整數型別隱式轉換成浮點型別會保留整數部分,但是有時會丟失精度,這是因為浮點型別總是有比整數型別更大的數值,但是可能只有更少的精度,

所有的整數型別都能隱式轉換成 decimal 型別,因為小數型別能表示所有可能的整數值,其他所有的數值型別轉換成小數型別或從小數型別轉換到數值型別必須是顯式轉換,

算術運算子 (+ 、 - 、 * 、 / 、 %) 用于除了 8 位和 16 位的整數型別之外的所有數值型別,

自增和自減運算子 (++ 、 --) 給數值加 1 或減 1 ,這兩個運算子可以放在變數的前面或后面,這取決于你想讓變數在計算運算式之前還是之后被更新,

(P022)

整數型別的除法運算總是會截斷余數,用一個值為 0 的變數做除數將產生一個運行時錯誤 (DivisionByZeroException) ,

用字面值 0 做除數將產生一個編譯時錯誤,

整數型別在運行算術運算時可能會溢位,默認情況下,溢位默默地發生而不會拋出任何例外,盡管 C# 規范不能預知溢位的結果,但是 CLR (通用語言運行時) 總是會造成溢位行為,

checked 運算子的作用是在運行時當整型運算式或陳述句達到這個型別的算術限制時,產生一個 OverflowException 例外而不是默默的失敗,

checked 運演算法在有 ++ 、 -- 、 + 、 - (一元運算子和二元運算子) 、 * 、 / 和整數型別間顯式轉換運算子的運算式中起作用,

checked 運算子對 double 和 float 資料型別沒有作用,對 decimal 型別也沒有作用 (這種型別總是受檢的),

checked 運算子能用于運算式或陳述句塊的周圍,

可以通過在編譯時加上 /checked+ 命令列開關 (在 Visual Studio 中,可以在 Advanced Build Settings 中設定) 來默認使程式中所有運算式都進行算術溢位檢查,如果你只想禁用指定運算式或陳述句的溢位檢查,可以用 unchecked 運算子,

(P023)

無論是否使用了 /checked 編譯器開關,編譯時的運算式計算總會檢測溢位,除非應用了 unchecked 運算子,

C# 支持如下的位運算子 : ~ (按位取反) 、 & (按位與) 、 | (按位或) 、 ^ (按位異或) 、 << (按位左移) 、 >> (按位右移) ,

8 位和 16 位整數型別指的是 byte 、 sbyte 、 short 和 ushort ,這些型別缺少它們自己的算術運算子,所以 C# 隱式把它們轉換成所需的大一些型別,

不同于整數型別,浮點型別包含某些操作要特殊對待的值,這些特殊的值是 NaN (Not a Number) 、 +∞ 、 -∞ 和 -0 ,

float 和 double 型別包含用于 NaN 、 +∞ 、 -∞ 值 (MaxValue 、 MinValue 和 Epsilon) 的常量,

(P024)

非零值除以零的結果是無窮大,

零除以零或無窮大減去無窮大的結果是 NaN,

使用比較運算子 (==) 時,一個 NaN 的值永遠也不等于其他的值,甚至不等于其他的 NaN 值,

必須使用 float.IsNaN 或 double.IsNaN 方法來判斷一個值是不是 NaN ,

無論何時使用 object.Equals 方法,兩個 NaN 的值都是相等的,

NaN 在表示特殊值時很有用,

float 和 double 遵循 IEEE 754 格式型別規范,原生支持幾乎所有的處理器,

double 型別在科學計算時很有用,

decimal 型別在金融計算和計算那些 “人為” 的而非真實世界的值時很有用,

(P025)

float 和 double 在內部是基于 2 來表示數值的,因此只有基于 2 表示的數值才能被精確的表示,事實上,這意味著大多數有小數的字面值 (它們基于10) 將無法精確的表示,

decimal 基于 10,它能夠精確地表示基于10的數值 (也包括它的因子,基于2和基于5) ,因為實型字面值是基于 10 的,所以 decimal 能精確地表示像 0.1 這樣的數,然而,double 和 decimal 都不能精確表示那些基于 10 的極小數,

C# 中的 bool (System.Boolean 型別的別名) 能表示 true 和 false 的邏輯值,

盡管布爾型別值僅需要 1 位存盤空間,但是運行時卻用 1 位元組空間,這是因為位元組是運行時和處理器能夠有效使用的最小單位,為避免在使用陣列時的空間浪費,.NET Framework 提供了 System.Collections 命名空間下的 BitArray 類,它被設定成每個布林值使用 1 位,

bool 不能轉換成數值型別,反之亦然,

== 和 != 運算子用于判斷任何型別相等還是不相等,總是回傳一個 bool 值,

(P026)

對于參考型別,默認情況的相同是基于參考的,而不是底層物件的實際值,

相等和比較運算子 == 、 != 、 < 、 > 、 >= 和 <= 用于所有的數值型別,但是用于實數時要特別注意,

比較運算子也用于列舉 (enum) 型別成員,它比較列舉的潛在整數值,

&& 和 || 運算子用于判斷 “與” 和 “或” 條件,它們常常與代表 “非” 的 (!) 運算子一起使用,

&& 和 || 運算子會在可能的情況下執行短路計算,

短路計算在允許某些運算式時是必要的,

& 和 | 運算子也用于判斷 “與” 和 “或” 條件,

不同之處是 & 和 | 運算子不支持短路計算,因此它們很少用于代替條件運算子,

不同于 C 和 C++ , & 和 | 運算子在用于布爾運算式時執行布爾比較 (非短路計算) ,& 和 | 運算子只在用于數值運算時才執行位操作,

三元條件運算子 (簡稱為條件運算子) 使用 q ? a : b 的形式,它在條件 q 為真時,計算 a,否則計算 b ,

(P027)

條件運算式在 LINQ 陳述句中特別有用,

C# 中的 char (System.Char 型別的別名) 表示一個 Unicode 字符,它占用 2 個位元組,字符字面值在單引號 (') 中指定,

轉義字符不能按照字面表示或解釋,轉義字符由反斜杠(\)和一個表示特殊意思的字符組成,

\' 單引號
\" 雙引號
\\ 斜線
\0 空
\a 警告
\b 退格
\f 走紙
\n 換行
\r 回車
\t 水平制表符
\v 垂直制表符

\u (或 \x ) 轉義字符通過 4 位十六進制代碼來指定任意 Unicode 字符,

從字符型別到數值型別的隱式轉換只在這個數值型別可以容納無符號 short 型別時有效,對于其他的數值型別,則需要顯式轉換,

(P028)

C# 中的字串型別 (System.String 的別名) 表示一些不變的、按順序的 Unicode 字符,字串字面值在雙引號 (") 中指定,

string 型別是參考型別而不是值型別,但是它的相等運算子卻遵守值型別的語意,

對 char 字面值有效的轉移字符在字串中也有效,

C# 允許逐字字串字面值,逐字字串字面值要加前綴 @ ,它不支持轉義字符,

逐字字串字面值也可以貫穿多行,

可以通過在逐字字串中寫兩次的方式包含雙引號字符,

(+) 運算子連接兩個字串,

右面的操作物件可以是非字串型別的值,在這種情況下這個值的 ToString 方法將被呼叫,

既然字串是不變的,那么重復地用 (+) 運算子來組成字串是低效率的 : 一個更好的解決方案是用 System.Text.StringBuilder 型別,

(P029)

字串型別并不支持 < 和 > 的比較,必須使用字串型別的 CompareTo 方法,

陣列代表固定數量的特定型別元素,為了高效率地讀取,陣列中的元素總是存盤在連續的記憶體塊中,

陣列用元素型別后加方括號表示,

方括號也可以檢索陣列,通過位置讀取特定元素,

陣列索引從 0 開始,

陣列的 Length 屬性回傳陣列中的元素數量,一旦陣列被建立,它的長度將不能被更改,

System.Collection 命名空間和子命名空間提供了像可變陣列等高級資料結構,

陣列初始化陳述句定義了陣列中的每個元素,

所有的陣列都繼承自 System.Array 類,它提供了所有陣列的通用服務,這些成員包括與陣列型別無關的獲取和定義元素的方法,

建立陣列時總是用默認值初始化陣列中的元素,型別的默認值是值為 0 的項,

無論陣列元素型別是值型別還是參考型別都有重要的性能影響,若元素型別是值型別,每個元素的值將作為陣列的一部分進行分配,

(P030)

無論是任何元素型別,陣列本身總是參考型別物件,

多維陣列分為兩種型別 : “矩形陣列” 和 “鋸齒形陣列” , “矩形陣列” 代表 n 維的記憶體塊,而 “鋸齒形陣列” 則是陣列的陣列,

矩形陣列宣告時用逗號 (,) 分隔每個維度,

陣列的 GetLength() 方法回傳給定維度的長度 (從 0 開始) ,

鋸齒形陣列在宣告時用兩個方括號表示每個維度,

鋸齒形陣列內層維度在宣告時可不指定,

不同于矩形陣列,鋸齒形陣列的每個內層陣列都可以是任意長度;每個內層陣列隱式初始化成空 (null) 而不是一個空陣列;每個內層陣列必須手工創建,

有兩種方式可以簡化陣列初始化運算式,第一種是省略 new 運算子和型別限制條件,第二種是使用 var 關鍵字,使編譯器隱式確定區域變數型別,

(P032)

隱式型別轉換能進一步用于一維陣列的這種情況,能在 new 關鍵字之后忽略型別限制符,而由編譯器推斷陣列型別,

為了使隱式確定陣列型別正常作業,所有的元素都必須可以隱式轉換成同一種型別,

運行時給所有的陣列索引進行邊界檢查,如果使用了不合法的索引,就會拋出 IndexOutOfRangeException 例外,

和 Java 一樣,陣列邊界檢查對型別安全和簡化除錯是很有必要的,

通常來說,邊界檢查的性能消耗很小,即時編譯器會進行優化,像在進入回圈之前預先檢查所有的索引是不安全的,以此來避免在每輪回圈中都檢查索引,

C# 提供 "unsafe" 關鍵字來顯式繞過邊界檢查,

變數表示存盤著可變值的存盤空間,變數可以是區域變數、引數 (value 、 ref 或 out) 、 欄位 (instance 或 static) 或陣列元素,

“堆” 和 “堆疊” 是存盤變數和常量的地方,它們每個都有不同的生存期語意,

“堆疊” 是存盤區域變數和引數的記憶體塊,堆疊在進入和離開一個函式時邏輯增加和減少,

(P033)

“堆” 是指物件殘留的記憶體塊,每當一個新的物件被創建時,它就被分配進堆,同時回傳這個物件的參考,

當程式執行時,堆在新物件創建時開始填充,

.NET 運行時有垃圾回收器,它會定期從堆上釋放物件,

只要物件沒有被參考,他就會被選中釋放,

無論變數在哪里宣告,值型別實體以及物件參考一直存在,如果宣告的實體作為物件中的欄位或陣列元素,那么實體存盤于堆上,

在 C# 中你無法顯式洗掉物件,但在 C++ 中可以,未參考的物件最終被垃圾回收器回收,

堆也存盤靜態欄位和常量,不同于堆上被分配的物件 (可以被垃圾回收器回收),靜態欄位和常量將一直存在直到應用程式域結束,

C# 遵守明確賦值的規定,在實踐中,這是指在沒有 unsafe 背景關系情況下是不能訪問未初始化記憶體的,明確賦值有三種含義 :

1. 區域變數在讀取之前必須被賦值;

2. 當呼叫方法時必須提供函式的引數;

3. 其他的所有變數 (像欄位和陣列元素) 都自動在運行時被初始化;

(P034)

欄位和陣列元素都會用其型別的默認值自動初始化,

所有型別實體都有默認值,預定義型別的默認值是值為 0 的項 :

[型別] - [默認值]

所有參考型別 - null
所有數值和列舉型別 - 0
字符型別 - '\0'
布爾型別 - false

能夠對任何型別使用 default 關鍵字來獲得其默認值,

自定義值型別中的默認值與自定義型別定義的每個欄位的默認值相同,

方法有一連串的引數,其中定義了一系列必須提供給方法的引數,

(P035)

能通過 ref 和 out 修飾符來改變引數傳遞的方式 :

[引數修飾符] - [傳遞型別] - [必須明確賦值的引數]

none - 值型別 - 傳入
ref - 參考型別 - 傳入
out - 參考型別 - 傳出

通常,C# 中引數默認是按值傳遞的,這意味著在將引數值傳給方法時創建引數值的副本,

值傳遞參考型別引數將賦值給參考而不是物件本身,

(P036)

如果按參考傳遞引數,C# 使用 ref 引數修飾符,

注意 ref 修飾符在宣告和呼叫時都是必需的,這樣就清楚地表明了將執行什么,

ref 修飾符對于轉換方法是必要的,

無論引數是參考型別還是值型別,都可以實作值傳遞或參考傳遞,

out 引數和 ref 引數類似,除了 :

1. 不需要在傳入函式之前賦值;

2. 必須在函式結束之前賦值;

(P037)

out 修飾符通常用于獲得方法的多個回傳值,

和 ref 引數一樣, out 引數是參考傳遞,

當參考傳遞引數時,是為已存變數的存盤空間起了個別名,而不是創建了新的存盤空間,

params 引數修飾符在方法最后的引數中指定,它使方法接收任意數量的指定型別引數,引數型別必須宣告為陣列,

(P038)

也可以將通常的陣列提供給 params 引數,

從 C# 4.0 開始,方法、構造方法和索引器都可以被宣告成可選引數,只要在宣告時提供默認值,這個引數就是可選引數,

可選引數在呼叫方法時可以被省略,

編譯器在可選引數被用到的地方用了默認值代替了可選引數,

被其他程式集呼叫的 public 方法在添加可選引數時要求重新編譯所有的程式集,因為引數是強制的,

可選引數的默認值必須由常量運算式或無引數的值型別構造方法指定,可選引數不能被標記為 ref 或 out ,

強制引數必須在可選引數方法宣告和呼叫之前出現 (params 引數例外,它總是最后出現),

相反的,必須將命名引數和可選引數聯合使用,

命名引數可以按名稱而不是按引數的位置確定引數,

(P039)

命名引數能按任意順序出現,

不同的是引數運算式按呼叫端引數出現的順序計算,通常,這只對相互作用的區域有效運算式有所不同,

命名引數和可選引數可以混合使用,

按位置的引數必須出現在命名引數之前,

命名引數在和可選引數混合使用時特別有用,

如果編譯器能夠從初始化運算式中推斷出變數的型別,就能夠使用 var 關鍵字 (C# 3.0 中引入) 來代替型別宣告,

因為是直接等價,所以隱式型別變數是靜態指定型別的,

(P040)

當無法直接從變數宣告中推斷出變數型別時,var 關鍵字將降低代碼的可讀性,

運算式本質上表示的是值,最簡單的運算式是常量和變數,運算式能夠用運算子進行轉換和組合,運算子用一個或多個輸入運算元來輸出新的運算式,

C# 中的運算子分為一元運算子、二元運算子和三元運算子,這取決它們使用的運算元數量 (1 、 2 或 3) ,

二元運算子總是使用中綴標記法,運算子在兩個運算元中間,

基礎運算式由 C# 語言內置的基礎運算子運算式組成,

(. 運算子) 執行成員查找;

(() 運算子) 執行方法呼叫;

空運算式是沒有值的運算式,

因為空運算式沒有值,所以不能作為運算元來創建更復雜的運算式,

賦值運算式用 = 運算子將一個運算式的值賦給一個變數,

(P041)

賦值運算式不是空運算式,實際上它包含了賦值操作的值,因此能再加上另一個運算式,

復合賦值運算子是由其他運算子組合而成的簡化運算子,

當運算式包含多個運算子時,運算子的優先級和結合性決定了計算的順序,

優先級高的運算子先于優先級低的運算子執行,

如果運算子的優先級相同,那么運算子的結合性決定計算的順序,

二元運算子 (除了賦值運算子、 lambda 運算子 、 null 合并運算子) 是左結合運算子,換句話說,它們是從左往右計算,

賦值運算子、 lambda 運算子、 null 合并運算子和條件運算子是右結合運算子,換句話說,它們從右往左計算,右結合運算子允許多重賦值,

(P043)

函式包含按出現的字面順序執行的陳述句,陳述句塊是大括號 ({}) 中出現的一系列陳述句,

(P044)

宣告陳述句可以宣告新變數,也可以用運算式初始化變數,宣告陳述句以分號結束,可以用逗號分隔的串列宣告多個同型別的變數,

常量的宣告和變數宣告類似,除了不能在宣告之后改變它的值和必須在宣告時初始化,

區域變數和常量的作用范圍是在當前的陳述句塊中,不能在當前的或嵌套的陳述句塊中宣告另一個同名的區域變數,

變數的作用范圍是它所在的整個代碼段,

運算式陳述句是運算式也是合法的陳述句,運算式陳述句必須改變狀態或呼叫某些改變的狀態,改變的狀態本質上是指改變一個變數,

可能的運算式陳述句是 :

1. 賦值運算式 (包括自增和自減運算式) ;

2. 方法呼叫運算式 (有回傳值的和無回傳值的) ;

3. 物件實體化運算式;

(P045)

當呼叫有回傳值的建構式或方法時,并不一定要使用回傳值,除非建構式或方法改變了某些狀態,否則這些陳述句完全沒作用,

C# 有下面幾種陳述句來有條件地控制程式的執行順序 :

1. 選擇陳述句 (if, switch) ;

2. 條件陳述句 (? :) ;

3. 回圈陳述句 (while 、 do-while 、 for 、 foreach) ;

if 陳述句是否執行代碼體取決于布爾運算式是否為真,

如果代碼體是一條陳述句,可以省略大括號,

if 陳述句之后可以緊跟 else 分句,

在 else 分句中,能嵌套另一個 if 陳述句,

(P046)

else 分句總是與其前陳述句塊中緊鄰的未配對的 if 陳述句結合,

可以通過改變大括號的位置來改變執行順序,

大括號可以明確地表明結構,這能提高嵌套 if 陳述句的可讀性 (即使編譯器并不需要),

從語意上講,緊跟著每一個 if 陳述句的 else 陳述句從功能上都是嵌套在 else 陳述句之中的,

switch 陳述句可以根據變數可能值的選擇來轉移程式的執行,

switch 陳述句可以擁有比嵌套 if 陳述句更加簡短的代碼,因為 switch 陳述句只要求運算式計算一次,

(P047)

只能在支持靜態計算的型別運算式中使用 switch 陳述句,因此限制了它只適用于整數型別、字串型別和列舉型別,

在每個 case 分句的結尾,必須用某種跳轉陳述句明確說明下一步要執行的代碼,這里有選項 :

1. break (跳轉到 switch 陳述句結尾) ;

2. goto case x (跳轉到另一個 case 分句) ;

3. goto default (跳轉到 default 分句) ;

4. 任何其他的跳轉陳述句 —— return 、 throw 、 continue 或 goto 標簽;

當多于一個值要執行相同代碼時,可以按順序列出共同的 case 條件,

switch 陳述句的這種特性對于寫出比嵌套 if-else 陳述句更清晰的代碼來說很重要,

C# 能夠用 while 、 do-while 、 for 和 foreach 陳述句重復執行一系列陳述句,

while 回圈在布爾運算式為真時重復執行一段代碼,這個運算式在回圈體被執行之前被檢測,

(P048)

do-while 回圈在功能上不同于 while 回圈的是它在陳述句塊執行之后檢測運算式 (保證陳述句塊至少被執行一次) ,

for 回圈類似有特殊分句的 while 回圈,這些特殊分句用于初始化和累積回圈變數,

for 回圈有下面的3個分句 :

for (initialization-clause; condition-clause; interation-clause) {statement-or-statement-block}

initialization-clause : 在回圈之前執行,用于初始化一個或多個回圈變數;
condition-clause : 是布爾運算式,當它為真時,將執行回圈體;
interation-clause : 在每次回圈陳述句體之后執行,通常用于更新回圈變數;

for 陳述句的3個部分都可以被省略,可以通過下面的代碼來實作一個無限回圈 (也可以用 while(true) 代替) : for (;;)

(P049)

foreach 陳述句遍歷可列舉物件的每一個元素,大多數 C# 和 .NET Framework 中表示集合或元素串列的型別都是可列舉的,

陣列和字串都是可列舉的,

C# 中的跳轉陳述句有 break 、 continue 、 goto 、 return 和 throw ,

跳轉陳述句違背了 try 陳述句的可靠性規則,這意味著 :

1. 跳轉到 try 陳述句塊之外的跳轉總是在到達目的地之前執行 try 陳述句的 finally 陳述句塊;

2. 跳轉陳述句不能從 finally 陳述句塊內跳到塊外;

break 陳述句用來結束回圈體或 switch 陳述句體的執行,

continue 陳述句放棄回圈體中其后的陳述句,繼續下一輪回圈,

(P050)

goto 陳述句用于轉移執行到陳述句塊中的另一個標簽處,或者用于 switch 陳述句內,

標簽陳述句僅僅是陳述句塊中的占位符,用冒號后綴表示,

goto case case-constant 用于轉移執行到 switch 陳述句塊中的另一個條件,

return 陳述句退出方法,如果這個方法有回傳值,同時必須回傳方法指定回傳型別的運算式,

return 陳述句能出現在方法的任意位置,

throw 陳述句拋出例外來表示有錯誤發生,

using 陳述句用于呼叫在 finally 陳述句塊中實作 IDisposable 介面的 Dispose 方法,

C# 多載了 using 關鍵字,使它在不同背景關系中有不同的含義,

特別注意 using 指令不同于 using 陳述句,

(P051)

lock 陳述句是呼叫 Monitor 類 Enter() 方法和 Exit() 方法的簡化操作,

命名空間是型別名稱必須唯一的作用域,型別通常被組織到分層的命名空間里,這樣既避免了命名沖突又使型別名更容易被找到,

命名空間組成了型別名的基本部分,

命名空間是獨立于程式集的,

程式集是像 .exe 或 .dll 一樣的部署單元,

命名空間不影響成員的可見性 —— public 、 internal 、 private 等,

namespace 關鍵字為其中的型別定義了命名空間,

命名空間中的點 (.) 表明嵌套命名空間的層次結構,

可以用包含從外到內的所有命名空間的完全限定名來指代一種型別,

如果型別沒有在任何命名空間中被定義,則說明它存在于全域命名空間內,

全域命名空間也包含了頂級命名空間,

using 指令用于匯入命名空間,這是不使用完全限定名來指代某種型別的便捷方法,

(P052)

在不同命名空間定義相同型別名稱是合法的 (而且通常是需要的),

外層命名空間中宣告的名稱能夠直接在內層命名空間中使用,

如果想使用同一命名空間分層結構的不同分支中的型別,你就要使用部分限定名,

如果相同的型別名出現在內層和外層命名空間中,內層的型別優先,如果要使用外層命名空間中的型別,必須使用它的完全限定名,

(P053)

所有的型別名在編譯時都被轉換成完全限定名,中間語言 (IL) 代碼不包含非限定名和部分限定名,

可以重復宣告同一命名空間,只要它里面的型別名不沖突,

我們能在命名空間中使用嵌套 using 指令,可以在命名空間宣告中指定 using 指令的范圍,

(P054)

引入命名空間有可能引起型別名的沖突,因此可以只引入需要的型別而不是整個命名空間,為每個型別創建別名,

外部別名允許參考兩個完全限定名相同的型別,這種特殊情況只發生在兩種型別來自不同的程式集,

(P055)

內層命名空間中的名稱隱藏了外層命名空間中的名稱,但是,有時候即使使用型別的完全限定名也無法解決沖突,

(::) 用于限定命名空間別名,

【第03章】

(P057)

類是最常見的一種參考型別,

復雜的類可能包含一下內容 :

1. 類屬性 —— 類屬性及類修飾符,非嵌套的類修飾符有 : public 、 internal 、 abstract 、 sealed 、 static 、 unsafe 、 partial ;

2. 類名 —— 各種型別引數、唯一基類,多個介面;

3. 花括號內 —— 類成員 (方法、成員屬性、索引器、事件、欄位、構造方法、運算子函式、嵌套型別和終止器) ;

欄位是類或結構體中的變數,

以下修飾符可以用來修飾欄位 :

[靜態修飾符] —— static

[訪問權限修飾符] —— public internal private protected

[繼承修飾符] —— new

[不安全代碼修飾符] —— unsafe

[只讀修飾符] —— readonly

[跨執行緒訪問修飾符] —— volatile

(P058)

“只讀修飾符” 防止欄位值在構造后被更改,只讀欄位只能在宣告時或在其所屬的類構造方法中被賦值,

欄位不一定要初始化,沒有被初始化的欄位系統會賦一個默認值 ( 0 、 \0 、 null 、 false ) ,欄位初始化陳述句在構造方法之前執行,

為了簡便,可以用逗號分隔的串列宣告一組同型別的欄位,這是宣告具有共同屬性和修飾符的一組欄位的簡潔寫法,

方法是用一組陳述句實作某個行為,方法能從呼叫陳述句的特定型別的傳入引數中接收輸入資料,并把輸出資料以特定的回傳值型別回傳給呼叫陳述句,方法也可以回傳 void 型別,表明這個方法不向呼叫方回傳任何值,此外,方法還可以通過 ref / out 引數向呼叫方回傳值,

方法簽名在整個類中必須是唯一的,方法簽名包括方法名、引數型別 (但不包括引數名及回傳值型別) ,

方法可以用以下的修飾符 :

[靜態修飾符] —— static

[訪問權限修飾符] —— public internal private protected

[繼承修飾符] —— new virtual abstract override sealed

[部分方法修飾符] —— partial

[非托管代碼修飾符] —— unsafe extern

只要確保方法簽名不同,可以在類中多載方法 (多個方法共用同一個方法名) ,

回傳值型別和引數修飾符不屬于方法簽名的一部分,

引數是按值傳遞還是按參考傳遞,也是方法簽名的一部分,

構造方法執行類或結構體的初始化代碼,構造方法的定義和方法的定義類似,區別僅在于構造方法名和回傳值只能和封裝它的類相同,

(P059)

構造方法支持以下修飾符 :

[訪問權限修飾符] —— public internal private protected

[非托管代碼修飾符] —— unsafe extern

類或結構體可以多載構造方法,為了避免重復編碼,一個構造方法可以用 this 關鍵字呼叫另一個構造方法,

(P060)

當一個構造方法呼叫另一個時,被呼叫的構造方法先執行,

C# 編譯器自動為沒有顯式定義構造方法的類生成構造方法,但是,一旦顯式定義了構造方法,系統將不再生成無引數構造方法,

對于結構體來說,無引數構造方法是結構體所固有的,因此,不能自己定義,結構體的隱式構造方法的作用是用默認值初始化每個欄位,

欄位初始化按宣告的先后順序,在構造方法之前執行,

構造方法不一定都是公有的,通常,定義非公有的構造方法的原因是為了在一個靜態方法中控制類實體的創建,

靜態方法可以用于從池中回傳類物件,而不必創建一個新物件實體,或用來根據不同的輸入屬性回傳不同的子類,

(P061)

為了簡化類物件的初始化,可以在呼叫構造方法的陳述句中直接初始化物件的可訪問欄位或屬性,

使用臨時變數是為了確保在初始化程序中如果拋出例外,不會得到一個初始化未完成的物件,

物件初始化器是 C# 3.0 引入的新概念,

(P062)

如果想使程式在不同版本的程式集中保持二進制兼容,最好避免在公有方法中使用可選引數,

this 參考指的是參考類實體自身,

this 參考也用來避免類欄位和區域變數或屬性相混淆,

this 參考僅對類或結構體的非靜態成員有效,

屬性內部像方法一樣包含邏輯,

屬性和欄位的宣告很類似,但屬性比欄位多了一個 get / set 塊,

(P063)

get 和 set 提供屬性的訪問器,

讀取屬性值時會運行 get 訪問器,它必須回傳屬性型別的值,

給屬性賦值時,運行 set 訪問器,它有一個命名為 value 的隱含引數,型別和屬性型別相同,值直接被指定給私有欄位,

盡管訪問屬性和欄位的方法相同,但不同之處在于,屬性在獲取和設定值時,給實作者提供了完全的控制能力,這種控制能力使得實作者可以選擇所需的任何的內部通信機制,而無需將屬性的內部細節暴露給用戶,

在實際應用中,為了提高封裝性,可能更多地在公有欄位上應用公有屬性,

屬性可以用下面的修飾符 :

[靜態修飾符] —— static

[訪問權限修飾符] —— public internal private protected

[繼承修飾符] —— new virtual abstract override sealed

[非托管代碼修飾符] —— unsafe extern

如果只定義了 get 訪問器,屬性就是只讀的;如果定義了 set 訪問器,屬性就是只寫的,但很少用到只寫屬性,

通常屬性會用一個簡短的后臺欄位來存盤其所代表的資料,但屬性也可以從其他資料計算出來,

屬性最常見的實作方法是 get 訪問器和 set 訪問器,對一個同型別的私有欄位進行簡單的讀寫操作,自動屬性的宣告表明由編譯器提供上述實作方法,編譯器會自動產生一個后臺的私有欄位,該欄位名由編譯器生成,且不能被參考,

如果希望屬性對外暴露成只讀屬性, set 訪問器可以標記為 private 的,

在 C# 3.0 中引入了自動屬性,

get 和 set 訪問器可以有不同的訪問級別,

注意,屬性本身被宣告具有較高的訪問權限,然后在需要較低級別的訪問器上添加較低級別的訪問權限修飾符,

C# 屬性訪問器在系統內部被編譯成名為 get_XXX 和 set_XXX 的方法,

簡單的非虛擬屬性訪問器被 JIT (即時) 編譯器編譯成行內的,消除了屬性和欄位訪問方法的性能差別,行內是一種優化方法,它用方法的函式體替代方法呼叫,

通過 WinRT 的屬性,編譯器就可以假設是 put_XXX 命名轉換,而不是 set_XXX ,

索引器為訪問類或結構體中封裝的串列或字典型資料元素提供了自然的訪問介面,索引器和屬性很相似,但索引器通過索引值而非屬性名訪問資料元素,

string 類具有索引器,可以通過 int 索引訪問其中的每一個 char 值,

當索引是整型時,使用索引器的方法類似于使用陣列,

索引器和屬性具有相同的修飾符,

要撰寫一個索引器,首先定義一個名為 this 的屬性,將引數定義放在一對方括號中,

(P065)

如果省略 set 訪問器,索引器就變成只讀的,

索引器在系統內部被編譯成名為 get_Item 和 set_Item 的方法,

常量是值永遠不會改變的欄位,常量在編譯時靜態賦值,并且在使用時,編譯器直接替換該值,類似于 C++ 中的宏,常量可以是內置的資料型別 : bool 、 char 、 string 或列舉型別,

常量用關鍵字 const 定義,并且必須以特定值初始化,

常量在使用時比靜態只讀欄位有更多限制 : 不僅能使用的型別有限,而且初始化欄位的陳述句含義也不同,常量和靜態只讀變數的不同之處還有,常量是在編譯時賦值的,

(P066)

靜態只讀欄位可以在每個應用中有不同的值,

靜態只讀欄位的好處還有,當提供給其他程式集時,可以更新數值,

從另一角度看,將來可能發生變化的任意值都不受其定義約束,所以不應該表示為一個常量,

常量也可以在方法內宣告,

常量可以使用以下修飾符 :

[訪問權限修飾符] —— public internal private protected

[繼承修飾符] —— new

靜態構造方法是每個類執行一次,而不是每個類實體執行一次,一個類只能定義一個靜態構造方法,并且必須沒有引數,必須和類同名,

運行時在使用類之前自動呼叫靜態構造方法,下面兩種行為可以觸發靜態建構式 :

1. 實體化類;

2. 訪問類的靜態成員;

靜態構造方法只有兩個修飾符 : unsafe 和 extern ,

如果靜態構造方法拋出一個未處理例外,類在整個應用程式的生命周期內都是不可用的,

(P067)

靜態欄位在呼叫靜態構造方法之前執行初始化,如果一個類沒有靜態構造方法,欄位在類被使用前初始化或在運行時隨機選一個更早的時間執行初始化 (這說明靜態構造方法的存在可能使欄位初始化比正常時間晚執行),

靜態欄位按欄位宣告的先后順序初始化,

類可以標記為 static ,表明它必須僅由靜態成員組成,并且不能產生子類,

System.Console 和 System.Math 類就是靜態類的最好示例,

終止器是只能在類中使用的方法,它在垃圾收集器回收沒有被應用的物件前執行,

終止器的語法是類名加前綴 (~) ,

實際上,這是多載物件的 Finalize() 方法的 C# 語法,

(P068)

終止器允許使用以下修飾符 :

[非托管代碼修飾符] —— unsafe

區域類允許一個類分開定義,典型的用法是分開在多個檔案中,從其他源檔案自動生成的類需要和自定義的方法互動時,通常使用 partial 類,

每個類必須由 partial 宣告,

區域類的各組成部分不能有沖突的成員,

區域類完全由編譯器處理,也就是說,各組成部分在編譯時必須可用,并必須編譯在同一個程式集中,

有兩個方法為 partial 類定義基類 : 在每個部分定義同一個基類、僅在其中一部分定義基類,

每個部分都可以獨立定義并實作介面,

區域類可以包含區域方法,這些方法使自動生成的區域類可以為自定義方法提供自定義鉤子 (hook) ,

(P069)

區域方法由兩部分組成 : 定義和實作,定義一般由代碼生成器產生,而實作多為手工撰寫,

如果沒有提供方法的實作,方法的定義會被編譯器清除,這使得自動代碼生成可以自由提供鉤子 (hook) ,而不用擔心代碼過于臃腫,

區域方法必須是 void 型,并且默認是 private 的,

區域方法在 C# 3.0 中引入,

為了擴展或自定義原類,類可以繼承另一個類,繼承類讓你可以重用另一個類的方法,而無需重新構建,

一個類只能繼承自唯一的類,但可以被多個類繼承,從而形成類的層次,

子類也被稱為派生類;基類也被稱為超類,

(P070)

參考是多型的,意味著 X 型別的變數可以指向 X 子類的物件,

多型性之所以能實作,是因為子類具有基類的全部特征,反過來,則不正確,

物件參考可以被 :

1. 隱式向上轉換成基類的參考;

2. 顯式向下轉換為子類的參考;

在可兼容的型別參考之間向上型別轉換或向下型別轉換即為參考轉換 : 生成一個新的參考指向同一個物件,向上轉換總是能成功,而向下轉換只有在物件的型別符合要求時才能成功,

向上型別轉換創建一個基類指向子類的參考,

向上轉換以后,被參考的物件本身不會被替換或改變,

(P071)

向下型別轉換創建一個子類指向基類的參考,

對于向上轉換,只影響了參考,被參考的物件沒有變化,

向下轉換必須是顯式轉換,因為它可能導致運行時錯誤,

如果向下轉換出錯,會拋出 InvalidCastException ,

as 運算子在向下型別轉換出錯時為變數賦值 null (而不是拋出例外) ,

這個操作相當有用,接下來只需判斷結果是否為 null ,

如果不用判斷結果是否為 null ,使用 cast 更好,因為如果發生錯誤,cast 會拋出描述更清楚的例外,

as 運算子不能用來實作自定義轉換,也不能用于數值型轉換,

as 和 cast 運算子也可以用來實作向上型別轉換,但不常用,因為隱式轉換就可以實作,

is 運算子用于檢查參考的轉換能否成功,換句話說,它是檢查一個物件是否是從某個特定類派生 (或是實作某個介面),經常在向下型別轉換前使用,

(P072)

is 運算子不能用于自定義型別轉換和數值型型別轉換,但它可以用于拆箱機制的型別轉換,

標識為 virtual 的函式可以被提供特定實作的子類多載,

方法、屬性、索引器和事件都可以被宣告為 virtual ,

子類通過 override 修飾符多載虛方法,

虛方法和多載方法的標識、回傳值以及訪問權限必須完全一致,

多載方法可以通過 base 關鍵字呼叫其基類的實作,

從構造方法呼叫虛方法可能很危險,因為撰寫子類的人在重寫方法時不可能知道正在操作一個未完全實體化的物件,換而言之,重寫方法最侄訓訪問到一些依賴于未被構造方法初始化的域的方法或屬性,

被宣告為 abstract 的抽象類不能被實體化,只有抽象類的具體實作子類才能被實體化,

抽象類中可以定義抽象成員,抽象成員和虛成員相似,但抽象成員不提供默認的實作,實作必須由子類提供,除非子類也被宣告為抽象類,

(P073)

基類和子類可能定義相同的成員,

有時需要故意隱藏一個成員,這種情況下,可以在子類中使用 new 修飾符,

new 修飾符的作用僅為防止編譯器發出警告,

修飾符 new 把你的意圖傳達給編譯器以及其他編程人員,即重復的成員不是無意的,

C# 在不同的背景關系環境中使用 new 關鍵字表達完全不同的含義,特別要注意 new 運算子和 new 成員修飾符的不同,

(P074)

多載的方法成員可用 sealed 關鍵字密封它的實作,以防止該方法被它的更深層次的子類再次多載,

可以在類中使用 sealed 修飾符來密封整個類,含義是密封類中所有的虛方法,

密封類比密封方法成員更常見,

關鍵字 base 和關鍵字 this 很類似,它有兩個重要目的 :

1. 從子類訪問多載的基類方法成員;

2. 呼叫基類的構造方法;

(P075)

子類必須宣告自己的構造方法,

子類必須重新定義它想對外公開的任何構造方法,不過,定義子類的構造方法,也可以通過使用關鍵字 base 呼叫基類的某個構造方法實作,

關鍵字 base 和 this 用法類似,但 base 關鍵字呼叫的是基類中的構造方法,

基類的構造方法總是先執行,這保證了 base 的初始化發生在作為子類的特例初始化之前,

如果子類中的構造方法省略 base 關鍵字,那么基類的無參構造方法將被隱式呼叫,

如果基類沒有無引數的構造方法,子類的構造方法中就必須使用 base 關鍵字,

當物件被實體化時,初始化按以下順序進行 :

(1) 從子類到基類 : a. 初始化欄位 b. 指定被呼叫基類的構造方法中的變數;

(2) 從基類到子類 : a. 構造方法體執行;

(P076)

繼承對方法的多載有特殊的影響,

當多載被呼叫時,型別最明確的優先匹配,

具體呼叫哪個多載是靜態決定的 (編譯時) 而不是在運行時決定,

object 類 (System.Object) 是所有型別的最侄訓類,

任何型別都可以向上轉換成 object 型別,

(P077)

堆疊是一種遵循 LIFO (Last-In First-Out,后進先出法) 的資料結構,

堆疊有兩種操作 : push 表示一個元素進堆疊和 pop 表示一個元素出堆疊,

承載了類的優點,object 是參考型別,

當數值型別和 object 型別之間相互轉換時,公共語言運行時 (CLR) 必須作一些特定的作業,實作數值型別和參考型別的轉換這個程序被稱為裝箱和拆箱,

裝箱是將數值型別實體轉換成參考型別實體的行為,

參考型別可以是 object 類或介面,

拆箱需要顯式進行,

運行時檢查提供的值型別是否與真正的物件型別相匹配,并在檢查出錯誤時,拋出 InvalidCastException ,

(P078)

裝箱是把數值型別的實體復制到新物件中,而拆箱是把物件的內容復制回數值型別的實體中,

C# 在靜態 (編譯時) 和運行時都會進行型別檢查,

靜態型別檢查使編譯器能在程式沒有運行的情況下檢查正確性,

在參考或拆箱操作的向下型別轉換時,由 CLR 執行運行時型別檢查,

可以進行運行時型別檢查,是因為堆疊中的每個物件都在內部存盤了型別標識,這個標識可以通過呼叫 object 類的 GetType() 方法讀取,

所有 C# 的型別在運行時都會維護 System.Type 類的實體,有兩個基本方法可以獲得 System.Type 物件 :

1. 在類實體上呼叫 GetType 方法;

2. 在類名上使用 typeof 運算子;

GetType 在運行時賦值;typeof 在編譯時靜態賦值 (如果使用泛型型別,那么它將由即使編譯器決議),

(P079)

System.Type 有針對型別名、程式集、基類等的屬性,

同時 System.Type 還有作為運行時反射模式的訪問器,

ToString 方法回傳類實體的默認文本表述,這個方法被所有內置型別多載,

如果不重寫 ToString ,那么這個方法會回傳型別名稱,

當直接在數值型物件上呼叫像 ToString 這樣的多載的 object 成員時,不會發生裝箱,只有進行型別轉換時,才會執行裝箱操作,

(P080)

結構體和類相似,不同之處在于 :

1. 結構體是值型別,而類是參考型別;

2. 結構體不支持繼承 (除了隱式派生自 object 類的,更精確些說,是派生自 System.ValueType) ,

除了以下三項內容,結構體可以包含類的所有成員 :

1. 無引數的構造方法;

2. 終止器;

3. 虛成員;

當表示值型別時使用結構體更理想而不用類,

結構體是值型別,每個實體不需要在堆疊上實體化,

結構體的構造語意如下 :

1. 隱含存在一個無法多載的無引數構造方法,將欄位按位置零;

2. 定義結構體的構造方法時,必須顯式指定每個欄位;

3. 不能在結構體內初始化欄位;

(P081)

為了提高封裝性,類或類成員會在宣告中添加五個訪問權限修飾符之一,來限制其他類和其他程式集對它的訪問權限 :

[public] —— 完全訪問權限;“列舉型別成員” 或 “介面” 隱含的訪問權限;

[internal] —— 僅可訪問程式集和友元程式集;“非嵌套型別” 的默認訪問權限;

[private] —— 僅在包含型別可見;類和結構體 “成員” 的默認訪問權限;

[protected] —— 僅在包含型別和子類中可見;

[protected internal] —— protected 和 internal 的訪問權限并集 Eric Lippert 是這樣解釋的 : 默認情況下盡可能將所有成員定義為私有,然后每一個修飾符都會提高其訪問級別,所以用 protected internal 修飾的成員在兩個方面的訪問級別都提高了,

CLR 有對 protected 和 internal 訪問權限交集的定義,但 C# 并不支持,

(P082)

在高級語意應用中,加上 System.Runtime.CompilerServices.InternalsVisibleTo 屬性,就可以把 internal 成員提供給其他的友元程式集,

類權限是它內部宣告的成員訪問權限的封頂,關于權限封頂最常用的示例是 internal 類中的 public 成員,

當多載基類的函式時,多載函式的訪問權限必須一致,

(P083)

編譯器會阻止使用任何不一致的訪問權限修飾符,

子類可以比基類訪問權限低,但不能比基類訪問權限高,

介面和類相似,但介面只為成員提供定義而不提供實作,

介面和類的不同之處有 :

1. 介面的成員都是隱含抽象的,相反,類可以包含抽象成員和有具體實作的成員;

2. 一個類 (或結構體) 可以實作多個介面,相反,類只能繼承一個類,而結構體完全不支持繼承 (只能從 System.ValueType 派生),

介面宣告和類宣告很類似,但介面不提供其成員的實作,因為它的所有成員都是隱式定義為抽象的,這些成員將由實作介面的類或結構體實作,

介面只能包含方法、屬性、事件、索引器,這些正是類中可以定義為抽象的成員,

介面成員總是隱式地定義成 public 的,并且不能用訪問修飾符宣告,

實作介面意味著為其所有成員提供 public 的實作,

可以把物件隱式轉換為它實作的任意一個介面,

(P084)

介面可以從其他介面派生,

當實作多個介面時,有時成員識別符號會有沖突,顯式實作介面成員可以解決沖突,

呼叫顯式實作成員的唯一方法是先轉換為相應的介面,

(P085)

另一個使用顯式實作介面成員的原因是,隱藏那些和類的正常用法差異很大或有嚴重干擾的成員,

默認情況下,介面成員的實作是隱式定義為 sealed ,為了能多載,必須在基類中標識為 virtual 或者 abstract ,

顯式實作的介面成員不能標識為 virtual 的,也不能實作通常意義的多載,但是它可以被重新實作,

子類可以重新實作基類中已經被實作的任意一個介面,不管基類中該成員是不是 virtual 的,當通過介面呼叫時,重新實作都能夠屏蔽成員的實作,它不管介面成員是隱式還是顯式實作都有效,但后者效果更好,

(P086)

重新實作屏蔽僅當通過介面呼叫成員時有效,從基類呼叫時無效,

(P087)

將結構體轉換成介面會引發裝箱機制,呼叫結構體的隱式實作介面成員不會引發裝箱,

列舉型別是一種特殊的數值型別,可以在列舉型別中定義一組命名的數值常量,

(P088)

每個列舉成員都對應一個整數型,默認情況下 :

1. 對應的數值是 int 型的;

2. 按列舉成員的宣告順序,自動指定的常量為 0 、 1 、 2 ······ ;

可以指定其他的整數型別代替默認型別,

也可以顯式指定每個列舉成員對應的值,

編譯器還支持顯式指定部分列舉成員,沒有指定的列舉成員,在最后一個顯式指定的值的基礎上遞增,

列舉型別的實體可以和它對應的整型值互相顯式轉換,

也可以顯式地將一個列舉型別轉換成另一個,

兩個列舉型別之間的轉換通過對應的數值進行,

在列舉運算式中,編譯器對數值 0 進行特別處理,不需要顯式轉換,

(P089)

對 0 進行特別管理原因有兩個 :

1. 第一個列舉成員經常被用作 “默認” 值;

2. 在合并列舉型別中,0 表示不標識型別;

列舉型別成員可以合并,為了避免混淆,合并列舉型別的成員要顯式指定值,典型的增量為 2 ,

使用位運算子操作合并列舉型別的值,例如 | 和 & ,它們作用在對應的整型數值上,

依照慣例,當列舉型別元素被合并時,一定要應用 Flags 屬性,

如果宣告了一個沒有標注 Flags 屬性的列舉類型,列舉型別的成員仍然可以合并,但是當在該列舉實體上呼叫 ToString 方法時,輸出一個數值而非一組名字,

一般來說,合并列舉型別通常用復數名而不用單數名,

位運算子、算數運算子和比較運算子都回傳對應整型值的運算結果,

列舉型別和整型之間可以做加法,但兩個列舉型別之間不能做加法,

因為列舉型別可以和它對應的整型值相互轉換,列舉的真實值可能超出列舉型別成員的數值范圍,

位操作和算數操作也會產生非法值,

(P090)

檢查列舉值的合法性,靜態方法 Enum.IsDefined 有此功能,

Enum.IsDefined 對標識列舉型別不起作用,

(P091)

嵌套型別是宣告在另一個型別內部的型別,

嵌套型別有如下特征 :

1. 可以訪問包含它的外層類中的私有成員、以及外層類所能訪問的所有內容;

2. 可以使用所有的訪問權限修飾符,而不僅限于 public 和 internal ;

3. 嵌套型別的默認訪問權限是 private 而不是 internal ;

4. 從外層類以外訪問嵌套型別,需要用外層類名稱限定 (就像訪問靜態成員一樣);

所有型別都可以被嵌套,但只有類和結構體才能嵌套其他型別,

(P092)

嵌套型別在編譯器中的應用也很普遍,如編譯器用于生成捕獲迭代和匿名方法結構狀態的私有類,

如果使用嵌套型別的主要原因是避免一個命名空間中型別定義雜亂無章,那么可以考慮使用嵌套命名空間,使用嵌套型別的原因,應該是利用它較強的訪問控制能力,或者是因為嵌套型別必須訪問其外層類的私有成員,

C# 對書寫能跨型別復用的代碼,有兩個不同的支持機制 : 繼承和泛化,但繼承的復用性來自基類,而泛化的復用性是通過帶有 “占位符” 類的 “模板” ,和繼承相比,泛化能提高型別的安全性以及減少型別的轉換和裝箱,

C# 的泛化和 C++ 的模板是相似概念,但它們的作業方法不同,

泛型中宣告型別引數 —— 占位符型別,由泛型的使用者填充,它支持型別變數,

(P093)

在運行時,所有泛型的實體都是關閉的 —— 占位符型別填充,

只有在類或方法內部,T 才可以被定義為型別引數,

泛化是為了代碼能跨型別復用而設計的,

泛化方法指在方法的識別符號內宣告類引數,

(P094)

通常不需要提供引數的型別給泛化方法,因為編譯器可以在后臺推斷出型別,

在泛型中,只有新引入型別引數的方法才被歸為泛化方法 (用尖括號標出) ,

唯有方法和類可以引入型別引數,屬性、索引器、事件、欄位、構造方法、運算子都不能宣告型別引數,雖然它們可以參與使用所在的類中已經宣告的型別引數,

構造方法可以參與使用已存在的型別引數,但不能引入新的型別引數,

可以在宣告類、結構體、介面、委托和方法時引入型別引數,其他的結構 (如屬性) 不能引入型別引數,但可以使用型別引數,

泛型類或泛型方法可以有多個引數,

(P095)

泛型類名和泛型方法名可以被多載,只要型別引數的數量不同即可,

習慣上,泛型類和泛型方法如果只有一個型別引數,只要引數的含義明確,一般把這個型別引數命名為 T ,當使用多個型別引數時,每個型別引數都使用 T 作為前綴,后面跟一個更具描述性的名稱,

在運行時不存在開放的泛型 : 開放泛型被匯編成程式的一部分而關閉,但運行時可能存在無系結 (unbound) 泛型,只用作類物件,C# 中唯一指定無系結泛型的方法是使用 typeof 運算子,

開放泛型型別一般與反射 API 一起使用,

可以用 default 關鍵字獲取賦給泛型類引數的默認值,參考型別的默認值是 null ,數值型別的默認值是將類的所有欄位位置 0 ,

默認情況下,型別引數可以被任何型別替換,在型別引數上應用約束,可以定義型別引數為指定型別,

where T : base-class // 基類約束
where T : interface // 介面約束
where T : class // 參考型別約束
where T : struct // 數值型別約束 (排除可空型別)
where T : new() // 無引數構造方法約束
where U : T // 裸型別約束

(P096)

約束可以應用在方法和類的任何型別引數的定義中,

“基類約束” 或 “介面約束” 規定型別引數必須是某個類的子類或實作特定類或介面,這允許引數類可以被隱式轉換成特定類或介面,

“類約束” 和 “結構體約束” 規定 T 必須是參考型別或數值型別 (不能為空),

“無引數構造方法約束” 要求 T 有一個公有的無引數構造方法,如果定義了這個約束,就可以在 T 中呼叫 new() ,

“裸型別約束” 要求一個型別引數從另一個型別引數派生,

(P097)

泛型類和非泛型的類一樣,都可以作為子類,子類可以讓基類中的型別引數保持開放,

子類也可以用具體型別關閉泛型引數,

子類還可以引入新的型別變數,

技術上,子型別中所有型別引數都是新的 : 可以說子型別關閉后又重新開放了基類的基類引數,這表明子類可以為其重新打開的型別引數使用更有意義的新名稱,

當關閉型別引數時,類可以用自己作為物體類,

對每個封裝的類來說,靜態資料是全域唯一的,

(P098)

C# 的型別轉換運算子可以進行多種轉換,包括 :

1. 數值型轉換;

2. 參考型轉換;

3. 裝箱 / 拆箱 轉換;

4. 自定義轉換 (通過運算子多載) ;

根據原資料的型別,在編譯時決定轉換成何種型別,并實作轉換,因為編譯時還不知道原資料的確切型別,使得泛型引數具有有趣的語意,

(P099)

假定 S 是 B 的子類,如果 X<S> 允許參考轉換成 X<B> ,那么稱 X 為協變類,

由于 C# 符號的共變性 (和逆變性) ,所以 “可改變” 表示可以通過隱式參考轉換進行改變 —— 如 A 是 B 的子類,或者 A 實作 B,數字轉換、裝箱轉換和自定義轉換都不包含在內,

C# 4.0 中,泛化介面支持協變 (泛化委托也支持) ,但泛化類不支持,陣列也支持協變 (如 S 是 B 的子類,S[] 可以轉換成 B[]) ,

為了保證靜態類的安全性,泛化類不是協變的,

(P100)

由于歷史原因,陣列 array 型別具有協變性,

在 C# 4.0 中,泛化介面對用 out 修飾符標注的型別引數支持協變,和陣列不同,out 修飾符保證了協變性的介面是完全型別安全的,

T 前的 out 修飾符是 C# 4.0 的新特性,表明 T 只用在輸出的位置,

介面中的協變和逆變的典型應用是使用介面 : 很少需要向協變性介面寫入,確切地說,由于 CLR 的限制,為了協變性將方法引數標注為 out 是不合法的,

(P101)

不管泛型還是陣列,協變 (逆變) 僅對參考轉換的元素有效而對裝箱轉換無效,

泛化介面支持逆變當泛型引數只出現在輸入的位置,且被指定了 in 修飾符時,

【第04章】

(P103)

委托將方法呼叫者和目標方法動態關聯起來,

代理型別定義了代理實體可呼叫的方法,

(P104)

委托實體實際上是呼叫者的代表 : 呼叫者先呼叫委托,然后委托呼叫目標方法,這種間接呼叫方式可以將呼叫者和目標方法分開,

呼叫委托和呼叫方法類似 (因為委托的目的僅僅是提供一定程式的間接性) ,

委托和回呼相似,是捕獲 C 函式指標等結構體的一般方法,

委托變數動態指定呼叫的方法,這個特性對于撰寫插入式方法非常有用,

(P105)

所有的委托實體都有多播能力,意思是一個委托實體不僅可以參考一個目標方法,而且可以參考一組目標方法,用運算子 + 和 += 聯合多個委托實體,

委托按照添加的順序依次被觸發,

運算子 - 和 -= 從左邊的委托運算元中移除右邊的委托運算元,

可以在委托變數上 + 或 += null 值,等價于為變數指定一個新值,

同樣,在只有唯一目標方法的委托上呼叫 -= 等價于為該變數指定 null 值,

委托是不可變的,因此呼叫 += 或 -= 的實質是創建一個新的委托實體,并把它賦值給已有變數,

如果多播委托有非 void 的回傳型別,呼叫者從最后一個觸發的方法接識訓傳值,前面的方法仍然被呼叫,但回傳值都被丟棄了,大部分情況下呼叫的多播委托都回傳 void 型別,所以這個細小的差別就沒有了,

所有委托型別都是從 System.MulticastDelegate 派生的,System.MulticastDelegate 繼承自 System.Delegate,C# 將委托中使用的 + 、 - 、 += 和 -= 都編譯成 System.Delegate 的靜態 Combine 和 Remove 方法,

(P106)

當委托物件指向一個實體方法時,委托物件不僅需維護到方法的參考,而且需維護到方法所屬類實體的參考, System.Delegate 類的 Target 屬性表示這個類實體 (當委托參考靜態方法時為 null) ,

(P107)

委托類可以包含泛型引數,

public delegate T Transformer<T>(T arg);

有了泛化委托,我們就可以寫非常泛化的小型委托類,它們可以為具有任意回傳型別和任意多引數的方法服務,

(P108)

在 Framework 2.0 之前,并不存在 Func 和 Action 代理 (因為那時還不存在泛型),由于有這個歷史問題,所以 Framework 的許多代碼都使用自定義代理型別,而不使用 Func 和 Action ,

能用委托解決的問題,都可以用介面解決,

在下面的情形中,委托可能是比介面更好的選擇 :

1. 介面內只定義一個方法;

2. 需要多播能力;

3. 訂閱者需要多次實作介面;

(P109)

即使簽名相似,委托類也互不兼容,

如果委托實體指向相同的目標方法,則認為它們是等價的,

如果多播委托按照相同的順序參考相同的方法,則認為它們是等價的,

當呼叫一個方法時,可以給方法的引數提供大于其指定型別的變數,這是正常的多型行為,基于同樣的原因,委托也可以有大于它目標方法引數型別的引數,這稱為逆變,

(P110)

標準事件模式的設計宗旨是在其使用公共基類 EventArgs 時應用逆變,

如果呼叫一個方法,得到的回傳值型別可能大于請求的型別,這是正常的多型性行為,基于同樣的原因,委托的回傳型別可以小于它的目標方法的回傳值型別,這被稱為協變,

如果要定義一個泛化委托型別,最好按照如下準則 :

1. 將只用在回傳值的型別引數標注為協變 (out) ;

2. 將只用在引數的型別引數標注為逆變 (in) ;

(P111)

當使用委托時,一般會出現兩種角色 : 廣播者和訂閱者,

廣播者是包含委托欄位的類,它決定何時呼叫委托廣播,

訂閱者是方法目標的接收者,通過在廣播者的委托上呼叫 += 和 -= ,決定何時開始和結束監聽,一個訂閱者不知道也不干涉其他的訂閱者,

事件是使這一模式正式化的語言形態,事件是只顯示委托中 廣播 / 訂閱 需要的子特性的結構,使用事件的主要目的在于 : 保護訂閱互不影響,

宣告事件最簡單的方法是,在委托成員的前面加上 event 關鍵字,

(P113)

.NET 框架為事件定義了一個標準模式,它的目的是保持框架和用戶代碼之間的一致性,

標準事件模式的核心是 System.EventArgs —— 預定義的沒有成員的框架類 (不同于靜態 Empty 屬性) ,

EventArgs 是用于為事件傳遞資訊的基類,

考慮到復用性,EventArgs 子類根據它包含的內容命名 (而非根據將被使用的事件命名),它一般以屬性或只讀欄位將資料,

定義了 EventArgs 的子類,下一步是選擇或定義事件的委托,需遵循三條原則 :

1. 委托必須以 void 作為回傳值;

2. 委托必須接受兩個引數 : 第一個是 object 類,第二個是 EventArgs 的子類,第一個引數表明事件的廣播者,第二個引數包含需要傳遞的額外資訊,

3. 委托的名稱必須以 EventHandler 結尾,

框架定義一個名為 System.EventHandler<>的泛化委托,該委托滿足如下條件 :

public delegate void EventHandler<TEventArgs> (object source, TEventArgs e) where TEventArgs : EventArgs

(P114)

最后,該模式要求寫一個受保護的 (protected) 虛方法引發事件,方法名必須和事件名一致,以 On 作前綴,并接受唯一的 EventArgs 引數,

(P115)

如果事件不傳遞額外的資訊,可以使用預定義的非泛化委托 EventHandler ,

(P116)

事件訪問器是對 += 和 -= 功能的實作,默認情況下,訪問器由編譯器隱式實作,

編譯器把它轉換為 :

1. 一個私有的委托欄位;

2. 一對公有的事件訪問器函式,它們實作私有委托欄位的 += 、 -= 運算;

通過自定義事件訪問器,指示 C# 不要產生默認的欄位和訪問器邏輯,

顯式定義的事件訪問器,可以在委托的存盤和訪問上進行更復雜的操作,有以下三種常用情形 :

1. 當事件訪問器僅為廣播該事件的另一個類作交接;

2. 當類定義了大量事件,而大部分時間有很少訂閱者,這種情況下,最好在字典中存盤訂閱者的委托實體,因為字典比大量的空委托欄位的參考需要更少的存盤開銷;

3. 當顯式實作宣告事件的介面時;

事件的 add 和 remove 部分被編譯成 add_XXX 和 remove_XXX 方法,

和方法相似,事件可以是虛擬的 (virtual) 、多載的 (overriden) 、抽象的 (abstract) 或密封的 (sealed) ,事件還可以是靜態的 (static),

(P117)

Lambda 運算式是寫在委托實體上的匿名方法,

編譯器立即將 Lambda 運算式轉換成下面兩種情形其中的一種 :

1. 委托實體;

2. Expression<Tdelegate> 型別的運算式樹,該運算式樹將 Lambda 運算式內的代碼顯示為可遍歷的物件模式,這使得對 Lambda 運算式的解釋可以延遲到運行時,

編譯器在內部將這種 Lambda 運算式編譯成一個私有方法,并把運算式代碼移到該方法中,

Lambda 運算式有以下形式 : (引數) => 運算式或陳述句塊,

為了方便,在只有一個可推測型別的引數時,可以省略小括號,

Lambda 運算式使每個引數和委托的引數一致,運算式的引數 (可以為 void) 和委托的回傳值型別一致,

Lambda 運算式代碼除了可以是運算式還可以是陳述句塊,

Lambda 運算式通常和 Func 或 Action 委托一起使用,因此可以將前面的運算式寫成下面的形式,

(P118)

Lambda 運算式是 C# 3.0 中引入的概念,

編譯器通常可以根據背景關系推斷出 Lambda 引數的型別,但當不能推斷時,必須明確指定每個引數的型別,

Lambda 運算式可以參考方法內的內部變數和引數 (外部變數) ,

Lambda 運算式參考的外部變數稱為捕獲變數,捕獲變數的運算式稱為一個閉包,

捕獲的變數在真正呼叫委托時被賦值,而不是在捕獲時賦值,

Lambda 運算式可以自動更新捕獲變數,

捕獲變數的生命周期可以延伸到和委托的生命周期相同,

(P119)

在 Lambda 運算式內實體化的區域變數,在每次呼叫委托實體期間是唯一的,

在內部捕獲是通過把被捕獲的變數 “提升” 到私有類的欄位實作的,當方法被呼叫時,實體化該類,并將其生命周期系結在委托的實體上,

當捕獲 for 或 foreach 陳述句中的回圈變數時,C# 把這些回圈變數看做是宣告在回圈外部的,這表明每個回圈捕獲的是相同的變數,

(P120)

匿名方法是 C# 2.0 引入的特性,并通過 C# 3.0 的 Lambda 運算式得到大大擴展,

匿名方法類似于 Lambda 運算式,但沒有下面的特性 :

1. 確定型別的引數;

2. 運算式語法 (匿名方法必須是陳述句塊) ;

3. 在指定到 Expression<T> 時,編譯成運算式樹的功能;

寫匿名方法的方法是 : delegate 關鍵字后面跟引數宣告 (可選) ,然后是方法體,

(P121)

完全省略引數宣告是匿名方法獨有的特性 —— 即使委托需要這些引數宣告,

匿名方法和 Lambda 運算式使用同樣的方法捕獲外部變數,

try 陳述句是為了處理錯誤或清理代碼而定義的陳述句塊,try 塊后面必須跟有 catch 塊或 finally 塊或兩個塊都有,

當 try 塊執行發生錯誤時,執行 catch 塊;當結束 try 塊時 (如果當前是 catch 塊,則當結束 catch 塊時),不管有沒有發生錯誤,都執行 finally 塊來清理代碼,

catch 塊可以訪問 Exception 物件,該物件包含錯誤資訊,catch 中可以彌補錯誤也可以再次拋出例外,當僅僅是記錄錯誤或要拋出更高層次的錯誤時,我們選擇再次拋出例外,

finally 塊在程式中起決定作用,因為任何情況下它都被執行,通常用于清除任務,

(P122)

例外處理需要幾百個時鐘周期,代價相對較高,

當拋出例外時,公共語言運行時 CLR 詢問 : 當前是否在能捕獲例外的 try 陳述句塊中運行 ?

1. 如果是,執行轉到相應的 catch 塊,如果 catch 塊成功地運行結束,執行轉到 try 下面的陳述句 (如果存在,finally 塊優先執行) ;

2. 如果否,執行跳轉到呼叫函式,重復上述詢問 (在執行 finally 塊之后) ;

如果沒有用于處理例外的函式,用戶將看到一個錯誤提示框,并且程式終止,

catch 子句定義捕獲哪些型別的例外,這些例外應該是 System.Exception 或 System.Exception 的子類,

捕獲 System.Exception 表示捕獲所有可能的例外,用于以下情況 :

1. 不管哪種特定型別的例外,程式都可以修復;

2. 希望重新拋出該例外 (可以在記入日志后);

3. 程式終止前的最后一個錯誤處理;

(P123)

更常見的做法是,為了避免處理程式沒有被定義的情況,只捕獲特定型別的例外,

可以在多個 catch 子句中處理各種例外型別,

對于每一種給定的例外,只有一個 catch 子句執行,如果想要建立捕獲更普遍的例外的安全網,必須把處理特定例外的陳述句放在前面,

如果不需要使用變數值,不指定變數也可以捕獲例外,

甚至,變數和型別可以都省略,表示指捕獲所有例外,

除 C# 外的其他語言中,可以拋出不是派生自 Exception 類的物件 (但不推薦) , CLR 自動把此物件封裝在 RuntimeWrappedException 類中 (該類派生自 Exception) ,

無論是否拋出例外,也不管 try 程式塊是否完全執行,finally 程式塊總是被執行,通常用 finally 程式塊來清除代碼,

在以下情況下執行 finally 程式塊 :

1. catch 塊執行完成;

2. 由于跳轉陳述句 (如 return 或 goto) 離開 try 塊;

3. try 塊結束;

(P124)

finally 塊為程式添加了決定性內容,在下面實體中,無論是否符合以下條件,打開的檔案總能被關閉 :

1. try 塊正常結束;

2. 因為是空檔案,提前回傳 EndOfStream ;

3. 讀取檔案時拋出 IOException 例外;

在 finally 塊中呼叫物件的 Dispose 方法是貫穿 .NET 框架的標準約定,且在 C# 的 using 陳述句中也明確支持,

許多類內部封裝了非托管資源,例如檔案管理、影像管理、資料庫連接等,這些類實作 System.IDisposable 介面,這個介面定義了一個名為 Dispose 的無引數方法,用于清除這些非托管資源,

using 陳述句提供了一種在 finally 塊中呼叫 IDisposable 介面物件的 Dispose 方法的優雅方法,

(P125)

可以在運行時或用戶代碼中拋出例外,

可以捕獲例外后再重新拋出,

如果將 throw 替換為 throw ex,那么這個例子仍然有效,但是新產生例外的 StackTrace 屬性不再反映原始的錯誤,

(P126)

重新拋出例外不會影響例外的 StackTrace 屬性,當重新拋出一個不同型別的例外時,可以設定 InnerException 屬性為原始的例外,這樣有利于除錯,幾乎所有型別的例外都可以實作這一目的,

System.Exception 類的最重要的屬性有下面幾個 :

1. StackTrace —— 表示從例外的起源到 catch 塊的所有方法的字串;

2. Message —— 描述例外的字串;

3. InnerException —— 導致外部例外的內部例外 (如果有的話) ,它本身還可能有另一個 InnerException ;

所有的 C# 例外都是運行時例外,沒有和 Java 對等的編譯時檢查例外,

下面的例外型別在 CLR 和 .NET 框架中廣泛使用,可以在程式中自主拋出這些例外或者將它們作為基類來派生自定義例外類 :

1. System.ArgumentException —— 當使用不恰當的引數呼叫函式時拋出,這通常表明程式有 bug ;

2. System.ArgumentNullException —— ArgumentException 的子類,當函式引數為 null (意料外的) 時拋出;

3. System.ArgumentOutOfRangeException —— ArgumentException 的子類,當屬性值太大或太小時拋出 (通常是數值型) ;

4. System.InvalidOperationException —— 不管是哪種特定的屬性值,當物件的狀態不符合方法正確執行的要求時拋出;

5. System.NotSupportedException —— 該例外拋出表示不支持特定功能;

6. System.NotImplementedException —— 該例外拋出表明某個方法還沒有具體實作;

7. System.ObjectDisposedException —— 當函式呼叫的物件已被釋放時拋出;

另一個常見的例外型別是 NullReferenceException ,當一個物件的值為 null 并訪問它的成員時,CLR 就會拋出這個例外 (表示代碼有 bug) ,

當方法出錯時,可以選擇回傳某種型別的錯誤代碼或拋出例外,一般情況下,如果錯誤發生在正常的作業流之外或者希望方法的直接呼叫者不進行錯誤處理時,拋出例外,但有些情況下最好給呼叫者提供兩種選擇,

如果型別決議失敗,Parse 方法拋出例外,TryParse 方法回傳 false ,

(P128)

Enumerator 是只讀的,且游標只能在順序值上向前移,實作下面物件之一 :

1. System.Collections.IEnumerator ;

2. System.Collections.Generic.IEnumerator<T> ;

從技術上講,任何具有 MoveNext 方法和 Current 屬性的物件,都被看作是 enumerator 型別的,

foreach 陳述句用來在可列舉的物件上執行迭代操作,可列舉物件是順序表的邏輯表示,它本身不是一個游標,但物件自身產生游標,

可列舉物件可以是 :

1. IEnumerable 或 IEnumerable<T> 的實作;

2. 具有名為 GetEnumerator 的方法回傳一個 enumerator ;

IEnumerator 和 IEnumerable 在 System.Collections 命名空間中定義,

IEnumerator<T> 和 IEnumerable<T> 在 System.Collection.Generic 命名空間中定義,

如果 enumerator 實作了 IDisposable ,那么 foreach 陳述句也起到 using 陳述句的作用,

(P129)

可以通過一個簡單的步驟實體化和填充可列舉的物件,它要求可列舉物件實作 System.Collections.IEnumerable 介面,并且有可呼叫的帶適當個數引數的 Add 方法,

和 foreach 陳述句是列舉物件的使用者相對,迭代器是列舉物件的生產者,

(P130)

return 陳述句表示該方法回傳的值,而 yield return 陳述句表示從本列舉器產生的下一個元素,

迭代器是包含一個或多個 yield 陳述句的方法、屬性或索引器,迭代器必須回傳以下四個介面之一 (否則,編譯器會報錯) :

// Enumerable 介面
System.Collections.IEnumerable
System.Collections.Generic.IEnumerable<T>

// Enumerator 介面
System.Collections.IEnumerator
System.Collections.Generic.IEnumerator<T>

回傳 enumerable 介面和回傳 enumerator 介面的迭代器具有不同的語意,

yield break 陳述句表明迭代器不回傳后面的元素而是提前結束,

(P131)

迭代器塊中使用 return 陳述句是不合法的,必須使用 yield break 陳述句來代替,

yield return 陳述句不能出現在帶 catch 子句的 try 陳述句塊中,

yield return 陳述句也不能出現在 catch 或 finally 陳述句塊中,出現這些限制的原因是編譯器必須將迭代器轉換為帶有 MoveNext 、 Current 和 Dispose 成員的普通類,而且轉換例外處理陳述句塊可能會大大增加代碼復雜性,

但是,可以在只帶 finally 陳述句塊的 try 塊中使用 yield 陳述句,

迭代器具有高度可組合性,

(P132)

迭代器模式的組合性在 LINQ 中是非常有用的,

參考型別可以表示一個不存在的值,即空參考,

(P133)

若要在數值型別中表示空值,必須使用特殊的結構即可空型別 (Nullable),可空型別是由資料型別后加一個 “?” 表示的,

T? 轉換成 System.Nullable<T> ,而 Nullable<T> 是一個輕量的不變結構,它只有兩個域,分別是 Value 和 HasValue ,System.Nullable<T> 實質上是很簡單的,

public struct Nullable<T> where T : struct
{
public T Value {get;}
public bool HasValue {get;}
public T GetValueOrDefault();
public T GetValueOrDefault(T defaultValue);
}

當 HasValue 為假時嘗試獲取 Value,程式會拋出一個 InvalidOperationException 例外,

當 HasValue 為真時,GetValueOrDefault() 會回傳 Value ,否則回傳 new T() 或者一個特定的自定義默認值,

T? 的默認值是 null ,

從 T 到 T? 的轉換是隱式的,而從 T? 到 T 的轉換則必須是顯式的,

顯式強制轉換與直接呼叫可空物件的 Value 屬性實際上是等價的,因此,當 HasValue 為假時,程式會拋出一個 InvalidOperationException 例外,

如果 T? 是裝箱的,那么堆中的裝箱值包含的是 T ,而不是 T? ,這種優化方式是可以實作的,因為裝箱值是一個可能已經賦值為空的參考型別,

(P134)

C# 允許通過 as 運算子對一個可空型別進行拆箱,如果強制轉換出錯,那么結果為 null ,

Nullable<T> 結構體并沒有定義諸如 < 、 > 或者 == 的運算子,盡管如此,下面的代碼仍然能夠正常編譯和執行,

運算子提升表示可以隱式地使用 T 的運算子來處理 T? ,

編譯器會基于運算子型別來執行空值邏輯,

提升 “等于運算子” 處理空值的方式與參考型別相似,這意味著兩個空值是相等的,而且 :

1. 如果只有一個運算元為空,那么結果不相等;

2. 如果兩個運算元都不為空,那么比較它們的 Value ;

(P135)

關系運算子的運算原則表明空值運算元的比較是無意義的,這意味著比較兩個空值或比較一個空值與一個非空值的結果都是 false ,

可以混合使用可空和不可空型別,這是因為 T 與 T? 之間存在隱式轉換機制,

如果運算元的型別是 bool? ,那么 & 和 | 運算子會將 null 作為一個未知值看待,所以,null | true 的結果為真,因為 :

1. 如果未知值為假,那么結果為真;

2. 如果未知值為真,那么結果為真;

(P136)

?? 運算子是空值合并運算子,它既可用來計算可空值型別,也可用來計算參考型別,也就是說,如果運算元不為空,直接計算;否則,計算器默認值,

?? 運算子的結果等同于使用一個顯式默認值呼叫 GetValueOrDefault ,除非當變數不為空時傳遞給 GetValueOrDefault 的運算式從未求值,

可空型別在將 SQL 映射到 CLR 時是非常有用的,

可空型別還可用于表示所謂環境屬性的后備欄位,如果環境屬性為空,那么回傳其父類的值,

(P137)

運算子可以經過多載實作更自然的自定義型別語法,運算子多載非常適合用來表示最普通的基本資料型別的自定義結構體,

下面的運算子也可以多載 :

1. 隱式和顯式轉換 (使用 implicit 和 explicit 關鍵字實作) ;

2. 常量 true 和 false;

下面的運算子可以間接進行多載 :

1. 復合賦值運算子 (例如 += 、 /=) 可以通過多載非復合運算子 (例如 + 、 /) 進行隱式多載;

2. 條件運算子 && 和 || 可以通過多載位運算子 & 和 | 進行隱式多載;

(P138)

運算子是通過宣告一個運算子函式進行多載的,運算子函式具有以下規則 :

1. 函式名是通過 operator 關鍵字及其后的運算子指定的;

2. 運算子函式必須標記為 static 和 public ;

3. 運算子函式的引數表示的是運算元;

4. 運算子函式的回傳型別表示的是運算式的結果;

5. 運算子函式所宣告的型別至少有一個運算元;

多載一個賦值運算子會自動支持相應的復合賦值運算子,

成對多載 : C# 編譯器要求邏輯上成對的運算子必須同時定義,這些運算子包括 (== 、 !=) 、 (< 、 >) 和 (<= 、 >=) ,

Equals 和 GetHashCode : 在大多數情況中,如果多載了 (==) 和 (!=) ,那么通常也需要多載物件中定義的 Equals 和 GetHashCode 方法,使之具有合理的行為,如果沒有按要求多載,那么 C# 編譯器將會發出警告,

IComparable 和 IComparable<T> : 如果多載了 (< 、 >) 和 (<= 、 >=),那么還應該實作 IComparable 和 IComparable<T> ,

(P139)

隱式和顯式轉換也是可多載的運算子,這些轉換經過多載后一般能使強關聯型別之間的轉換變得更加簡明和自然,

如果要在弱關聯型別之間進行轉換,那么更適合采用以下方式 :

1. 撰寫一個具有該轉換型別的引數的建構式;

2. 撰寫 ToXXX 和 (靜態) FromXXX 方法進行型別轉換;

(P140)

擴展方法允許一個現有型別擴展新的方法而不需要修改原始型別的定義,

擴展方法是靜態類的靜態方法,其中第一個引數需要使用 this 修飾符,型別就是擴展的型別,

(P141)

擴展方法是 C# 3.0 后增加的特性,

擴展方法類似于實體方法,也支持一種鏈接函式的方法,

只有命名空間在定義域內,我們才能夠訪問擴展方法,

任何兼容的實體方法總是優先于擴展方法,

如果兩個擴展方法名稱相同,那么擴展方法必須作為一個普通的靜態方法呼叫,才能夠區分所呼叫的方法,然而,如果其中一個擴展方法具有更具體的引數,那么有更具體引數的方法優先級更高,

(P143)

匿名型別是一個由編譯器臨時創建來存盤一組值的簡單類,如果要創建一個匿名型別,我們可以使用 new 關鍵字,后面加上物件初始化陳述句,在其中指定該型別包含的屬性和值,

必須使用 var 關鍵字來參考一個匿名型別,因為型別的名稱是編譯器產生的,

匿名型別的屬性名可以從本身是一個識別符號或以識別符號結尾的運算式得到,

如果這兩個匿名型別實體的元素是相同型別的,并且它們在相同的程式集中宣告,那么它們在內部是相同的型別,

匿名型別的 Equals 方法也被多載了,從而能夠執行正確的等于比較運算,

(P144)

匿名型別主要是在撰寫 LINQ 查詢時使用,并且是 C# 3.0 后才出現的特性,

動態系結是將系結 (決議型別、成員和操作的程序) 從編譯時延遲到運行時,

在編譯時,如果程式員知道某個特定函式、成員或操作的存在,而編譯器不知道,那么動態系結是很有用的,

這種情況通常出現在操作動態語言 (如 IronPython) 和 COM 時,而且如果不使用動態系結,就只能使用反射機制,

動態型別是通過背景關系關鍵字 dynamic 宣告的,

動態系結型別會告訴編譯器 “不要緊張” ,

無論系結的是什么樣的方法,其底線是已知系結是由編譯器實作的,而且系結是完全依賴于之前已經知道的運算元型別,這就是所謂的靜態系結,

(P145)

動態型別類似于 object ,同樣不表現為一種型別,其區別是能夠在編譯時在不知道它存在的情況下使用它,

動態物件是基于其運行時型別進行系結的,而不是基于編譯時型別,

當編譯器遇到一個動態系結運算式時 (通常是一個包含任意動態型別值的運算式) ,它僅僅對運算式進行打包,而系結則在后面的運行時執行,

在運行時,如果一個動態物件實作了 IDynamicMetaObjectProvider ,那么這個介面將用來執行系結,否則,系結的發生方式就幾乎像是編譯器已經事先知道動態物件的運行時型別一樣,我們將這兩種方式稱為自定義系結和語言系結,

COM 可認為是第三種系結方式,

自定義系結是通過實作了 IDynamicMetaObjectProvider (IDMOP) 而實作的,

(P146)

動態系結會損壞靜態型別安全性,但不會影響運行時型別安全性,與反射機制不同,不能通過動態系結繞過成員訪問規則,

靜態和動態系結之間最顯著的差異在于擴展方法,

動態系結也會對性能產生影響,然而,由于 DLR 的快取機制對同一個動態運算式的重復呼叫進行了優化,允許在一個回圈中高效地呼叫動態運算式,這個優化機制能夠使一個簡單的動態運算式的處理負載對硬體的性能影響控制在 100ms 以內,

如果一個成員系結失敗,那么程式會拋出 RuntimeBinderException 例外,可以將它看作是一個運行時的編譯錯誤,

dynamic 和 object 型別之間可以執行一個深度等值比較,在運行時,下面這個運算式的結果為 true :

typeof(dynamic) = typeof (object)

(P147)

與物件參考相似,動態參考可以指向除指標型別以外的任意型別的物件,

在結構上,物件參考和動態參考之間沒有任何區別,

動態參考可以直接在它所指的物件上執行動態操作,

動態型別會對其他所有型別進行隱式轉換,

如果要成功進行轉換,動態物件的運行時型別必須能夠隱式轉換到目標的靜態型別上,

(P148)

var 和 dynamic 型別表面上是相似的,但是它們實際上是有區別的 :

var 由編譯器確定型別,

dynamic 由運行時確定型別,

一個由 var 宣告的變數的靜態型別可以是 dynamic ,

域、屬性、方法、事件、建構式、索引器、運算子和轉換都是可以動態呼叫的,

dynamic 的標準用例是包含一個動態接受者,

然而,還可以使用動態引數呼叫已知的靜態函式,這種呼叫受到動態多載決議的影響,并且可能包括 :

1. 靜態方法;

2. 實體建構式;

3. 已知靜態型別的接收者的實體方法;

(P149)

動態型別用在動態系結中,但是,靜態型別在可能的情況下也用在動態系結中,

(P150)

有一些函式是不能夠動態呼叫的,如下 :

1. 擴展方法 (通過擴展方法語法) ;

2. 介面的所有成員;

3. 子類隱藏的基類成員;

擴展方法成為只適用于編譯時的概念,

using 指令在編譯后會消失 (當它們在系結程序中完成了將簡單的名稱映射到完整命名空間的任務之后) ,

(P151)

特性是添加自定義資訊到代碼元素 (程式集、型別、成員、回傳值和引數) 的擴展機制;

特性的一個常見例子是序列化,就是將任意物件轉換為一個特定格式或從特定格式生成一個物件的程序,在這情況中,某個欄位的屬性可以指定該欄位的 C# 表示方式和該欄位的表示方式之間的轉換,

特性是通過直接或間接地繼承抽象類 System.Attribte 的方式定義的,

如果要將一個特性附加到一個代碼元素中,那么就需要在該代碼元素之前用方括號指定特性的型別名稱,

編譯器能夠識別這個特性,如果某個標記為棄用的型別或成員被參考時,編譯器會發出警告,

按照慣例,所有特性型別都以 Attribute 結尾,C# 能夠識別這個后綴,也可以在附加一個屬性時省略這個后綴,

C# 語言和 .NET Framework 包含了大量的預定義特性,

特性可能具有一些引數,

特性引數分為兩類 : 位置和命名,

位置引數對應于特性型別的公開建構式的引數;命令引數則對應于該特性型別的公開欄位或公開屬性,

當指定一個特性時,必須包含對應于其中一個特性建構式的位置引數,命名引數則是可選的,

(P152)

特性目標不需要顯式指定,特性目標就是它后面緊跟的代碼元素而且一般是一個型別或型別成員,然而,也可以給程式集附加一些特性,這要求顯式地指定特性的目標,

一個代碼元素可以指定多個特性,每一個特性可以列在同一對方括號中 (用逗號分割) 或者在多對方括號中或者結合兩種方式,

從 C# 5 開始,可以給可選引數添加 3 個呼叫者資訊屬性中的一個,它們可以讓編譯器從呼叫者代碼獲取引數的默認值 :

1. [CallerMemberName] —— 表示呼叫者的成員名稱;

2. [CallerFilePath] —— 表示呼叫者的源代碼檔案路徑;

3. [CallerLineNumber] —— 表示呼叫者源代碼檔案的行號;

(P153)

呼叫者資訊特性很適合用于記錄日志以及實作一些模式,如當一個物件的某個屬性發生變化時,觸發一個變化通知事件,事實上,.NET 框架有一個專門實作這個效果的標準介面 INotifyPropertyChanged (位于 System.ComponentModel) ,

(P154)

C# 支持通過標記為不安全和使用 /unsafe 編譯器選項編譯的代碼塊中的指標直接進行記憶體操作,指標型別主要用來與 C 語言 API 進行互操作,但是也可用來訪問托管堆以外的記憶體,或者分析嚴重影響性能的熱點,

使用 unsafe 關鍵字標記一個型別、型別成員或陳述句塊,就可以在該范圍內使用指標型別和對記憶體執行 C++ 中的指標操作,

不安全代碼與對應的安全實作相比運行速度更快,

fixed 陳述句是用來鎖定托管物件的,

由于這可能對運行時效率產生一定的影響,所以 fixed 代碼塊只能短暫使用,而且堆分配應該避免出現在 fixed 代碼塊中,

(P155)

除了 & 和 * 運算子,C# 還支持 C++ 中的 -> 運算子,可以在結構體中使用,

我們可以在代碼中顯式地通過 stackalloc 關鍵字分配堆疊中的記憶體,由于這部分記憶體是從堆疊上分配的,所以其生命周期僅限于方法的執行時間,這點與其他的區域變數相同,這個代碼塊可以使用 [] 運算子實作記憶體索引,

我們也可以使用 fixed 關鍵字在一個結構體代碼塊中分配記憶體,

fixed 表示兩個不同的方面 : 大小固定和位置固定,

(P156)

空指標 (void*) 不給出假定底層資料的具體型別,它對于處理原始記憶體的函式是非常有用的,任意指標型別都可以隱式地轉換為 void* , void* 不可以被解除參考,算術運算子不能通過 void 指標執行,

指標也很適于訪問位于托管堆之外的資料 (如與 C DLL 或 COM 互動時) ,以及處理不在主存中的資料 (如圖形化記憶體或嵌入式設備的存盤介質) ,

(P157)

預處理指令向編譯器提供關于代碼范圍的額外資訊,最常用的預處理指令是條件指令,它提供了一種將某些代碼加入或排除出編譯范圍的方法,

通過 #if 和 #elif 指令,可以使用 || 、 && 和 ! 運算子在多個符號上執行或、與、非操作,

#error 和 #warning 符號會要求編譯器在遇到一些不符合要求的編譯符號時產生一條警告資訊或錯誤資訊,從而防止出現條件指令的偶然誤用,

(P158)

使用 Conditional 修飾的特性只有在出現指定的預處理符號時才編譯,

(P159)

檔案注釋是一種嵌入的、記錄型別或成員的 XML ,檔案注釋位于型別或成員宣告之前,以三個斜線開頭,

也可以采用以下方法 (注意開頭有兩個星號) ,/** */

如果使用 /doc 指令進行編譯,那么編譯器會將檔案注釋存盤到一個 XML 檔案中,并進行校對,這個特性主要有兩種作用 :

1. 如果與編譯的程式集位于同一個檔案夾,那么 Visual Studio 會自動讀取這個 XML 檔案,使用這些資訊向同名程式集的使用者提供 IntelliSense 成員清單;

2. 第三方工具 (如 Sandcastle 和 NDoc) 可以將 XML 檔案轉換成 HTML 幫助檔案;

【第05章】

(P163)

.NET Framework 中幾乎所有的功能都是通過大量的托管型別提供的,這些型別被組織成有層次的命名空間,并且被打包成一套程式集,與 CLR 一起構成 .NET 平臺,

有些 .NET 型別是由 CLR 直接使用的,并且對于托管的宿主環境而言是必不可少的,這些型別位于一個名為 mscorlib.dll 的程式集中,包括 C# 的內置型別,以及基本的集合類、流處理型別、序列化、反射、多執行緒和原生互操作性,

除此之外是一些附加型別,它們充實了 CLR 層面的功能,提供了其他一些特性,如 XML 、網路和 LINQ 等 ,這些型別位于 System.dll 、 System.Xml.dll 和 System.Core.dll 中,并且與 mscorlib 一起提供豐富的編程環境供 .NET Framework 的其他部分使用,

.NET Framework 的其余部分是由一些實用 API 組成的,主要包括以下三個方面的功能 :

1. 用戶介面技術;

2. 后臺技術;

3. 分布式系統技術;

C# 5.0 對應 CLR 4.5,這個版本比較特殊,因為它屬于 CLR 4.0 的補丁版本,

這意味著安裝 CLR 4.5 之后,目標平臺是 CLR 4.0 的應用實際上運行在 CLR 4.5 上,

(P164)

程式集和命名空間在 .NET Framework 中是相互交叉的,

(P164)

[.NET Framework 4.5 新特性]

Framework 4.5 新特性包括 :

1. 通過回傳 Task 的方法廣泛支持異步編程;

2. 支持 zip 壓縮協議;

3. 通過新增 HttpClient 類改進 HTTP 支持;

4. 改進垃圾收集器和程式集資源回收的性能;

5. 支持 WinRT 互操作性和開發 Metro 風格平板應用的 API ;

此外,還有一個新的 TypeInfo 類,以及可以指定與正則表達作業超過時間匹配的超時時間,

在并行計算領域,還有一個全新庫 Dataflow,可用于開發 生產者 / 消費者 風格的網格,

此外,WPF 、 WCF 和 WF (作業流基礎) 庫也有一些改進,

許多核心型別定義在以下程式集中 : mscorlib.dll 、 System.dll 和 System.Core.dll ,第一個程式集 mscorlib.dll 包括運行時環境本身所需要的型別;System.dll 和 System.Core.dll 包含程式員所需要的其他核心型別,

[.NET Framework 4.0 新特性]

Framework 4.0 增加了以下新特性 :

1. 新的核心型別 : BigInteger (大數字) 、 Complex (復數) 和元組;

2. 新的 SortedSet 集合;

3. 代碼協定,使方法能夠通過共同的義務和責任實作更可靠的互動;

4. 直接支持記憶體映射檔案;

5. 延遲的檔案和目錄 I / O 方法,它們回傳 IEnumerable<T> 而不是陣列;

6. 動態語言運行時 (DLR) 成為 .NET Framework 的一部分;

7. 安全透明,簡化了保證部分可信環境中程式庫安全性的方法;

8. 新的多執行緒結構,包括更強大的 Monitor.Enter 多載、新的信號發送類 (Barrier 和 CountdownEvent) 和延遲初始化原語;

9. 支持多核處理的并行計算 API ,包括 Parallel LINQ (PLINQ) 、命令式資料與任務并行性結構、支持并發的集合和低延遲同步機制與 spinning 原語;

10. 用于監控應用程式域資源的方法;

Framework 4.0 還包含了一些 ASP.NET 的改進,包括 MVC 框架和 Dynamic Data,以及 Entity Framework 、 WPF 、 WCF 和 Workflow 等方面的改進,此外,它還包含了新的 Managed Extensibility Framework 庫,以幫助運行時環境實作組合、發現和依賴注入,

(P165)

大多數的基礎型別都直接位于 System 命名空間,其中包括 C# 的內置型別、 Exception 基類、 Enum 、 Array 和 Delegate 基類、以及 Nullable 、 Type 、 DateTime 、 TimeSpan 和 Guid ,System 命名空間也包含執行數字計算功能 (Math) 、生成亂數 (Random) 和各種資料型別轉換 (Convert 和 BitConvert) 的型別,

System 命名空間還定義了 IDisposable 介面和與垃圾回收器互動的 GC 類,

在 System.Text 命名空間中有一個 StringBuilder 類,以及處理文本編碼的型別,

在 System.Text.RegularExpressions 命名空間中有一些執行基于模式的搜索和替換操作的高級型別,

.NET Framework 提供了各種處理集合專案的類,其中包括基于鏈表和基于字典的結構,以及一組統一它們常用特性的標準介面,

System.Collections //非泛型型別
System.Collections.Generic //泛型框架
System.Collections.Specialized //強型別框架
System.Collections.ObjectModel //自定義框架基類
System.Collections.ConCurrent //執行緒安全框架

(P166)

Framework 3.5 增加了語言集成查詢 (Language Integrated Query,LINQ) ,LINQ 允許對本地和遠程集合 (例如 SQL Server 表) 執行型別安全查詢,

LINQ 的最大優勢是提供了一種跨多個域的統一查詢 API ,

Metro 模板不包含整個 System.Data.* 命名空間,

LINQ to SQL 和 Entity Framework API 使用了 System.Data 命名空間的 ADO.NET 底層型別,

XML 在 .NET Framework 中被廣泛使用,同時也得到廣泛支持,

操作執行緒和異步操作的型別位于 System.Threading 和 System.Threading.Tasks 命名空間,

(P167)

Framework 提供了基于流的模型進行底層 輸入 / 輸出 操作,流一般用于檔案和網路連接的直接讀寫操作,它們可以被鏈接和封裝到裝飾流中,從而實作壓碩訓加密功能,

Stream 和 I / O 型別是在 System.IO 命名空間中定義的,

可以通過 System.Net 中的型別直接訪問標準的網路協議,如 HTTP 、 FTP 、 TCP / IP 和 SMTP ,

Framework 提供了幾個可以將物件保存為二進制或文本方式的系統,這些系統是分布式應用程式技術所必需的,如 WCF 、 Web Services 和 Remoting ,它們也可用于將物件保存到檔案和從檔案恢復物件,

Metro 模板不包含二進制序列化引擎,

C# 程式編譯產生的程式集包含可執行指令 (存盤為中間語言或 IL) 和元資料,它描述了程式的型別、成員和屬性,通過反射機制,可以在運行時檢查元資料或者執行某些操作,如動態呼叫方法,

通過 Reflection.Emit 可以隨時創建新代碼,

(P168)

動態編程的型別位于 System.Dynamic 中,

.NET Framework 具有自己的安全層,從而能夠將程式集裝入沙箱,甚至將自己裝入沙箱,

Metro 模板只包含 System.Security ;加密操作則在 WinRT 中處理,

C# 5 的異步函式可以顯著簡化并發編程,因為它們減少了底層技術的使用,然而,開發者有時候仍然需要使用信號發送結構、執行緒記憶體儲、讀 / 寫 鎖等,

執行緒型別位于 System.Threading 命名空間,

CLR 支持在一個行程中增加額外的隔離級別,即應用程式域,

AppDomain 型別定義在 System 命名空間中,

原生互操作性使您能夠呼叫未托管 DLL 中的函式、注冊回呼函式、映射資料結構和操作原生資料型別,COM 互操作性使您能夠呼叫 COM 型別和將 .NET 型別傳遞給 COM ,

.NET Framework 提供了 4 種支持基于用戶界面的應用程式的 API ,

1. ASP.NET (System.Web.UI) 撰寫運行在標準網頁瀏覽器上的瘦客戶端應用程式;

2. Silverlight 在網頁瀏覽器上實作富用戶界面;

3. Windows Presentation Foundation (System.Windows) 撰寫富客戶端應用程式;

4. Windows Forms (System.Windows.Forms) 支持遺留富客戶端應用程式;

(P169)

一般而言,瘦客戶端應用程式指的是網站;而富客戶端應用程式則是最終用戶必須下載或安裝在客戶端計算機上的程式,

富客戶端的方法是在客戶端和資料庫之間插入一個中間層,中間層運行在一臺遠程應用程式服務器上 (通常與資料庫服務器一起) ,并通過 WCF 、 Web Services 或 Remoting 與富客戶端通信,

在撰寫網頁時,可以選擇傳統的 Web Forms 或者新的 MVC (模型 - 視圖 - 控制器) API ,這兩種方法都基于 ASP.NET 基礎框架,從一開始,Framework 就支持 Web Forms ;MVC 則是在后來 Ruby on Rails 和 MonoRail 流行之后才出現的,

Web Forms 仍然適合用來撰寫主要包含靜態內容的網頁,

AJAX 的使用可以通過注入 jQuery 等庫進行簡化,

撰寫 ASP.NET 應用程式的型別位于 System.Web.UI 命名空間及其子命名空間中,并且屬于 System.Web.dll 程式集,

Silverlight 在技術上并不屬于 .NET Framework 的主框架 : 它是一個獨立的框架,包含了一部分的 Framework 核心特性,增加了作為網頁瀏覽器插件運行的功能,

(P170)

Silverlight 主要用于一些邊緣場景,

Windows Metro 庫同樣不屬于 .NET 框架,它只用于在 Windows 8 中開發平板電腦界面,

Metro API 源于 WPF 的啟發,并且使用 XAML 實作布局,其命名空間包括 Windows.UI 和 Windows.UI.Xaml ,

WPF 是在 Framework 3.0 時引入的,用來撰寫富客戶端應用程式,

WPF 的規模和復雜性使學習周期比較長,

撰寫 WPF 應用程式的型別位于 System.Windows 命名空間以及除 System.Windows.Forms 之外的所有子命名空間中,

與 WPF 相比,Windows Forms 相對簡單,它支持撰寫一般 Windows 應用程式時所需要使用的大多數特性,也能夠良好地兼容遺留應用程式,

Windows Forms 的學習程序相對簡單,并有豐富的第三方控制元件支持,

(P171)

Windows Forms 型別位于命名空間 System.Windows.Forms (在 System.Windows.Forms.dll 中) 和 System.Drawing (在 System.Drawing.dll) 中,其中后者包含了繪制自定義控制元件的 GDI+ 型別,

ADO.NET 是托管的資料訪問 API ,雖然它的名稱源于 20 世紀 90 年代的 ADO (ActiveX Data Objects) ,但是這兩種技術是完全不同的,

ADO.NET 包含兩個主要的底層組件 :

1. 提供者層 —— 提供者模型定義了資料庫提供者底層訪問的通用類和介面,這些介面包括連接、命令,配接器和讀取器 (資料庫的只向前的只讀游標) , Framework 包含對 Microsoft SQL Server 和 Oracle 的原生支持,具有 OLE-DB 和 ODBC 提供者,

2. DataSet 模型 —— 一個 DataSet 是一個資料的結構化快取,它類似于一個常駐記憶體的原始資料庫,其中定義了 SQL 結構,如表、記錄行、欄位、關系、約束和視圖,通過對資料快取的編程,可以減少資料庫的互動數量、增加服務器可擴展性以及加快富客戶端用戶界面的回應速度, DataSet 是可序列化的,它支持通過客戶端和服務器應用程式之間的線路傳輸,

提供者層只有兩個 API ,它們提供了通過 LINQ 查詢資料庫的功能 :

1. LINQ to SQL (從 Framework 3.5 開始引入) ;

2. Entity Framework (從 Framework 3.5 SP1 開始引入) ;

這兩種技術都包含 物件 / 關系 映射器 (ORM) ,意味著它們會自動將物件 (基于定義的類) 映射到資料庫的記錄行,這允許用戶通過 LINQ 查詢這些物件,而不需要撰寫 SQL 陳述句查詢并且不需要手動撰寫 SQL 陳述句進行物件更新,

DataSet 仍然是唯一能夠存盤和序列化狀態變化的技術 (這在多層應用程式中是非常有用的) ,

現在還沒有現成的便捷方法可以使用 Microsoft 的 ORM 來撰寫 N 層應用程式,

LINQ to SQL 比 Entity Framework 更簡單、更快速,并且一般會產生更好的 SQL ,Entity Framework 則更具靈活性,可以在資料庫和查詢的類之間創建復雜的映射,除了 SQL Server ,Entity Framework 還支持一些第三方資料庫,

Windows Workflow 是一個對可能長期運行的業務程序進行建模和管理的框架,Workflow 目標是成為一個標準的提供一致性和互操作性的運行時庫,Workflow 有助于減少動態控制的決策樹的編碼量,

Windows Workflow 嚴格意義上并不是一種后臺技術,可以在任何地方使用它,

Workflow 是從 .NET Framework 3.0 開始出現的,它的型別定義在 System.Workflow 命名空間中,實際上 Workflow 在 Framework 4.0 中進行了修改,增加的新型別位于 System.Activities 命名空間,

(P172)

Framework 允許通過 System.EnterpriseServices 命名空間中的型別與 COM+ 進行互操作,以實作諸如分布式事物等服務,它也支持通過 System.Messaging 中的型別使用 MSMQ (Microsoft Message Queuing) ,微軟訊息佇列實作異步的單向訊息傳遞,

WCF 是 Framework 3.0 引入的一個復雜的通信基礎架構,WCF 非常靈活且可配置,這使它的兩個前處理器 —— Remoting 和 (.ASMX) Web Services ,大多是冗余的,

WCF 、 Remoting 和 Web Services 很相似的方面就是它們都實作以下允許客戶端和服務器應用程式進行通信的基本模型 :

1. 在服務器端,可以指定希望遠程客戶端應用程式能夠呼叫的方法;

2. 在客戶端,可以指定或推斷將要呼叫的服務器方法的簽名;

3. 在服務器端和客戶端,都可以選擇一種傳輸和通信協議 (在 WCF 中,這是通過一個系結完成的) ;

4. 客戶端建立一個服務器連接;

5. 客戶端呼叫遠程方法,并在服務器上透明地執行;

WCF 會通過服務協定和資料協定進一步對客戶端和服務器進行解耦,概念上,客戶端會發送一條 (XML 或二進制) 訊息給遠程服務的終端,而非直接呼叫一個遠程方法,這種解耦方式的好處是客戶端不會依賴于 .NET 平臺或任意私有的通信協議,

WCF 是高度可配置的,它支持廣泛的標準化訊息協議,包括 WS-* ,

WCF 的另一個好處是可以直接修改協議,而不需要修改客戶端或服務器應用程式的其他內容,

與 WCF 通信的型別位于 System.ServiceModel 命名空間中,

Remoting 和 .ASMX Web Services 是 WCF 的前處理器,雖然 Remoting 仍然適合在相同行程中的應用程式域之間進行通信,但是它們在 WCF 中幾乎是冗余的,

Remoting 的功能針對一些緊密耦合的應用程式,

Web Services 針對一些低耦合或 SOA 型別應用程式,

Web Services 只能使用 HTTP 或 SOAP 作為傳輸和格式化協議,而應用程式一般是運行在 IIS 上,

互操作性的好處在于性能成本方面 —— Web Services 應用程式一般在執行和開發時間上的速度都比精心設計的 Remoting 應用程式慢,

Remoting 的型別位于 System.Runtime.Remoting 命名空間中;而 Web Services 的型別則位于 System.Web.Services 中,

(P173)

通過一個安全的 HTTP 通道進行連接時,WCF 允許通過 System.IdentityModel.Claims 和 System.IdentityModel.Policy 命名空間中的型別指定一個 CardSpace 身份,

【第06章】

(P174)

編程所需要的許多核心工具都不是由 C# 語言提供的,而是由 .NET Framework 中的型別提供的,

一個 C# 的 char 表示一個 Unicode 字符,它是 System.Char 結構體的別名,

System.Char 定義了許多處理字符的靜態方法,如 ToUpper 、 ToLower 和 IsWhiteSpace ,可以通過 System.Char 型別或它的別名 char 呼叫這些方法,

ToUpper 和 ToLower 會受到最終用戶的語言環境的影響,這可能會導致出現細微的缺陷,

(P175)

System.Char 、 System.String 還提供了針對語言變化的 ToUpper 和 ToLower ,它們加上后綴 Invariant ,

char 保留的大多數靜態方法都與字符分類有關,

(P176)

對于更細的分類,char 提供了一個名為 GetUnicodeCategory 的靜態方法,它回傳一個 UnicodeCategory 列舉值,

通過顯式轉換一個整數,可以產生一個位于 Unicode 集之外的 char ,要檢測字符的有效性,我們可以呼叫 char.GetUnicodeCategory : 如果結果是 UnicodeCategory.OtherNotAssigned ,那么這個字符就是無效的,

一個 char 占用 16 個二進制位,

C# 的 string (== System.String) 是一個不可變的 (不可修改的) 字符序列,

創建字串的最簡單的方法就是給變數定義一個字面值,

要創建一個重復的字符序列,可以使用 string 的建構式,

還可以從 char 陣列創建字串,而 ToCharArray 方法則是執行相反操作,

我們還可以多載 string 的構造方法來接受各種 (不安全的) 指標型別,以便創建其他型別字串,

空字串是長度為 0 的字串,如果要創建空字串,可以使用一個字母值或靜態的 string.Empty 欄位;如果要測驗空字串,可以執行一個等值比較或測驗它的 Length 屬性,

由于字串是參考型別,它們也可能是 null ,

(P177)

靜態的 string.IsNullOrEmpty 方法是測驗一個給定字串是 null 還是空白的快捷方法,

字串的索引器可以回傳一個指定索引位置的字符,與所有操作字串的方法相似,它是從 0 開始計數的索引,

string 還實作了 IEnumerable<char> ,所以可以用 foreach 遍歷它的字符,

在字串內搜索的最簡單方法是 Contains 、 StartsWith 和 EndsWith ,所有這些方法都回傳 true 或 false ,

Contains 方法并沒有提供這種多載的便利方法,但是可以使用 IndexOf 方法實作相同的效果,

IndexOf 方法更強大 : 它會回傳指定字符或子字串的首次出現位置 (-1 表示該子字串不存在) ,

StartsWith 、 EndsWith 和 IndexOf 都有多載方法,我們可以指定一個 StringComparison 列舉變數或 CultureInfo 物件,控制大小寫和文字順序,默認為使用當前文化規則執行區分大小寫的匹配,

LastIndexOf 與 IndexOf 類似,但是它是從后向前開始搜索的,

IndexOfAny 則回傳任意一系列字符的首次匹配位置,

LastIndexOfAny 則在相反方向執行相同的操作,

由于 String 是不可變的,所有 “處理” 字串的方法都會回傳一個新的字串,而原始字串則不受影響 (其效果與重新賦值一個字符變數一樣) ,

Substring 是取字串的一部分,

(P178)

如果省略長度,那么會得到剩余的字串,

Insert 和 Remove 會從一個指定位置插入或洗掉一些字符,

PadLeft 和 PadRight 會用特定字符將字串 (如果未指定,則使用空格) 填充成指定的長度,

如果輸入字串長度大于填充長度,那么回傳不發生變化的原始字串,

TrimStart 和 TrimEnd 會從字串的開始或結尾洗掉指定的字符;Trim 則用兩個方法執行洗掉操作,默認情況下,這些函式會洗掉空白字符 (包括空格、制表符、換行符和這些字符的 Unicode 變體) ,

Replace 會替換字串中出現的特定字符或子字串,

ToUpper 和 ToLower 會回傳輸入字串相應的大寫和小寫字符,默認情況下,它們會受用戶的當前語言設定的影響;ToUpperInvariant 和 ToLowerInvariant 總是采用英語字母表規則,

Split 接受一個句子,回傳一個單詞陣列,

默認情況下,Split 使用空白字符作為分隔符;經過多載后也可以接受包含 char 或 string 分隔符的 params 陣列,

Split 還可以選擇接受一個 StringSplitoptions 列舉值,它支持洗掉一些空項 : 這在一行單詞由多種分隔符分隔時很有用,

靜態的 Join 方法執行與 Split 相反的操作,它需要一個分隔符和字串陣列,

靜態的 Concat 方法與 Join 類似,但是它只接受字串陣列引數,并且沒有分隔符,

Concat 與 + 運算子效果完全相同 (實際上,編譯器會將 + 轉換成 Concat) ,

(P179)

靜態的 Format 方法提供了創建嵌入變數字串的便利方法,嵌入的變數可以是任意型別;而 Format 會直接呼叫它們的 ToString ,

包含嵌入變數的主字串稱為 “組合格式字串” ,呼叫 String.Format 時,需要提供一個組合格式字串,后面緊跟每一個嵌入式變數,

花括號里面的每一個數字稱為格式項,這些數字對應引數位置,后面可以跟 :

1. 逗號與應用的最小寬度;

2. 冒號與格式字串;

最小寬度用于對齊各個列,如果這個值為復數,那么資料就是左對齊;否則,資料就是右對齊的,

信用額度是通過 “C” 格式字串格式化為貨幣值,

組合格式字串的缺點是它很容易出現一些只有在運行時才能發現的錯誤,

進行兩個值比較時,.NET Framework 有兩個不同的概念 : 等值比較和順序比較,等值比較會判斷兩個實體在語意上是否是相同的;而順序比較則將兩個 (如果有) 實體按照升序或降序排列,然后判斷哪一個首先出現,

(P180)

等值比較并不是順序比較的一個子集,這兩種方法有各自不同的用途,

對于字串等值比較,可以使用 == 運算子或者其中一個字串的 Equals 方法,后者功能更強一些,因為它們允許指定一些選項,如區分大小寫,

另一個不同點是,如果變數被轉換成 object 型別,那么 == 就不一定是按字串處理,

對于字串順序比較,可以使用 CompareTo 實體方法或靜態的 Compare 和 CompareOrdinal 方法 : 這些方法會回傳一個正數、負數或 0 ,這取決于第一個值是在第二個值之后、之前還是同時出現,

字串比較有兩種基本的演算法 : 按順序的和區分文化的,順序比較會直接將字符決議為數字 (根據它們的 Unicode 數值);文化比較則參照特定的字母表來決議字符,特殊的文化有兩種 : “當前文化” ,這是基于計算機控制面板的設定;“不變文化” ,這在任何計算機上都是相同的,

對于等值比較,順序和特定文化的演算法都是很有用的,然而,在排序時,人們通常選擇詞義相關的比較 : 對字串按字母表排序時,需要一個字母順序表,順序比較則使用 Unicode 數字位置值,這可能會使英語字符按字母順序排序 —— 但是即使這樣也可能不滿足你的期望,

不變文化封裝了一個字母表,它認為大寫字符與其對應的小寫字符是相鄰的,

順序演算法將所有大寫字母排列在前面,然后才是全部小寫字符,

盡管順序比較有一些局限性,但是字串的 == 運算子總是執行區分大小寫的順序比較,當不帶引數呼叫時,string.Equals 的實體版本也是一樣的;這定義了 string 型別的 “默認” 等值比較行為,

字串的 == 和 Equals 函式選擇順序演算法的原因是它既高效又具有確定性,字串等值比較被認為是基礎操作,并且遠比順序比較的使用更頻繁,

等式的 “嚴格” 概念也與常見的 == 運算子用途保持一致,

(P181)

靜態方法會更適合一些,因為即使其中一個或兩個字串為 null 它也一樣有效,

String 的 CompareTo 實體方法執行區分文化和區分大小寫的順序比較,與 == 運算子不同,CompareTo 不使用順序比較 : 對于順序比較,區分文化的演算法更有效,

Compare 實體方法實作了 IComparable 泛型介面,這是在整個 .NET Framework 中使用的標準比較協議,這意味著字串的 CompareTo 定義了默認的順序行為字串,

所有順序比較的方法都會回傳正數、負數 或 0 ,這取決于第一個值是在第二個值之后、之前還是相同位置,

(P182)

StringBuilder 類 (System.Text 命名空間) 表示一個可變 (可編輯) 的字串,使用 StringBuilder ,可以 Append 、 Insert 、 Remove 和 Replace 子字串,而不需要替換整個 StringBuilder ,

StringBuilder 的構建函式可以選擇接受一個初始字串值,以及其內部容量的初始值 (默認是 16 個字符) ,如果需要更大的容量,那么 StringBuilder 會自動調整它的內部結構,以容納 (會有一些性能開銷) 最大的容量 (默認為 int.MaxValue) ,

StringBuilder 的一個普通使用方法是通過重復呼叫 Append 來創建一個長字串,這個方法比復雜連接普通字串型別要高效得多,

AppendLine 執行新添加一行字串 (在 Windows 中是 "\r\n") 的 Append 操作,

AppendFormat 接受一個組合格式字串,與 String.Format 類似,

除了 Insert 、 Remove 和 Replace 方法 (Replace 函式類似于字串的 Replace),StringBuilder 定義了一個 Length 屬性和一個可寫的索引器,可用來 獲取 / 設定 每個字串,

如果要清除 StringBuilder 的內容,我們可以創建一個新的 StringBuilder 或者將它的 Length 設為 0 ,

(P183)

將 StringBuilder 的 Length 設定為 0 不會減少它的內部容量,

Unicode 具有約一百萬個字符的地址空間,目前已分配的大約有十萬個,

.NET 型別系統的設計使用的是 Unicode 字符集,但是,ASCII 是隱含支持的,因為它是 Unicode 的子集,

UTF-8 對于大多數文本而言是最具空間效率的 : 它使用 1~4 個位元組來表示每個字符,

UTF-8 是最普遍的文本檔案和流的編碼方式 (特別是在互聯網上) ,它是 .NET 中默認的流 I / O 編碼方式 (事實上,它幾乎是所有語言隱含的默認編碼方式) ,

UTF-16 使用一個或兩個 16 位字來表示一個字符,它是 .NET 內部用來表示字符和字串的方式,有一些程式也使用 UTF-16 寫檔案,

UTF-32 是空間效率最低的 : 每一個代碼點直接對應一個 32 位數,所以每個字符都會占用 4 個位元組,因此,UTF-32 很少使用,然而,它可以簡化隨機訪問,因為每個字符都對應相同的位元組數,

System.Text 中的 Encoding 類是封裝文本編碼類的通用基本型別,它有一些子類,它們的作用是封裝各種編碼方式的相似特性,初始化一個正確配置類的最簡單方法是用一個標準的 IANA 名稱呼叫 Encoding.GetEncoding ,

最常用的編碼也可以通過專用的 Encoding 靜態屬性獲取,

(P184)

靜態的 GetEncodings 方法會回傳所有支持的編碼方式清單以及它們的標準 IANA 名稱,

Encoding 物件最常見的應用是控制檔案或流的文本讀寫操作,

UTF-8 是所有檔案和流 I / O 的默認文本編碼方式,

Encoding 物件和位元組陣列之間也可以進行互相轉換,GetBytes 方法將使用指定的編碼方式將 string 轉換為 byte[];而 GetString 則將 byte[] 轉換為 string ,

(P185)

.NET 將字符和字串存盤為 UTF-16 格式,

在 System 命名空間中有三個不可變結構可用來表示日期和時間 : DateTime 、 DateTimeOffset 和 TimeSpan ,而 C# 沒有定義與這些型別相對應的關鍵字,

TimeSpan 表示一段時間間隔或者是一天內的時間,對于后者,他就是一個 “時鐘” 時間 (不包括日期) ,它等同于從半夜 12 點開始到現在的時間 (假設沒有夏時制) ,TimeSpan 的最小單位為 100 納秒,最大值為 1 千萬天,可以為正數或負數,

創建 TimeSpan 的方法有三種 :

1. 通過其中一個構造方法;

2. 通過呼叫其中一個靜態的 From... 方法;

3. 通過兩個 DateTime 相減得到;

(P186)

如果希望指定一個單位的時間間隔,如分鐘、小時等,那么靜態的 From.. 方法更方便,

TimeSpan 多載了 < 、 > 、 + 和 - 運算子,

Total... 屬性則回傳表示整個時間跨度的 double 型別值,

靜態的 Parse 方法則執行與 ToString 相反的操作,它能將一個字串轉換為一個 TimeSpan ,

TryParse 執行與 ToString 相同的操作,但是當轉換失敗時,它會回傳 false ,而不是拋出例外,

XmlConvert 類也提供了符合標準 XML 格式化協議的 TimeSpan 字串轉換方法,

TimeSpan 的默認值是 TimeSpan.Zero ,

TimeSpan 也可用于表示一天內時間 (從半夜 12 點開始經過的時間) ,要獲得當前的時間,我們可以呼叫 DateTime.Now.TimeOfDay ,

(P187)

DateTime 和 DateTimeOffset 表示日期或者時間的不可變結構,它們的最小單位為 100 納秒,值的范圍從 0001 到 9999 年,

DateTimeOffset 是從 Framework 3.5 開始引入的,在功能上類似于 DateTime ,它的主要特性是能夠存盤 UTC 偏移值,這允許我們比較不同時區的時間值時得到更有意義的結果,

DateTime 和 DateTimeOffset 在處理時區方式上是不同的,DateTime 具有三個狀態標記,可表示 DateTime 是否與下列因素相關 :

1. 當前計算機的本地時間;

2. UTC (相當于現代的格林威治時間) ;

3. 不確定;

DateTimeOffset 更加特殊 —— 它將 UTC 的偏移量存盤為一個 TimeSpan ,

這會影響等值比較結果,而且是在 DateTime 和 DateTimeOffset 之間進行選擇的主要依據 :

1. DateTime 會忽略三個比較狀態標記,并且當兩個值的年、月、日、時、分等相等時就認為它們是相等的;

2. 如果兩個值參考相同的時間點,那么 DateTimeOffset 就認為它們是相等的;

夏時制會使這個結果差別很大,即使應用程式不需要處理多個地理時區,

在大多數情況中,DateTimeOffset 的等值比較邏輯會更好一些,

(P188)

如果在運行時指定與本地計算機相關的值,使用 DateTime 會更好,

DateTime 定義了能夠接受年、月和日以及可選的時、分、秒和毫秒的構造方法,

如果只指定日期,那么時間會被隱含地設定為半夜時間 (00:00:00) ,

DateTime 構造方法也允許指定一個 DateTimeKind —— 這是一個具有以下值的列舉值 : Unspecified 、 Local 、 Utc ,

這三個值與前一節所介紹的三個狀態標記相對應,

Unspecified 是默認值,它表示 DateTime 是未指定時區的,

Local 表示與當前計算機的本地時區相關,

本地 DateTime 不包含它參考了哪一個特定的時區,而且與 DateTimeOffset 不同的是,它也不包含 UTC 偏移值,

DateTime 的 Kind 屬性回傳它的 DateTimeKind ,

DateTime 的構造方法也經過多載從而可以接受 Calendar 物件 —— 允許使用 System.Globalization 中所定義的日歷子類指定一個時間,

DateTime 總是使用默認的公歷,

如果要使用另一個日歷進行計算,那么必須使用 Calendar 子類自己的方法,

也可以使用 long 型別的計數值 (ticks) 來創建 DateTime,其中計數值是從午夜開始算起的 100 納秒數,

在互操作性上,DateTime 提供了靜態的 FromFileTime 和 FromFileTimeUtc 方法來轉換一個 Windows 檔案時間 (由 long 指定),并且提供了 FromOADate 來轉換一個 OLE 自動日期 / 日期 (由 double 指定) ,

要從字串創建 DateTime,我們必須呼叫靜態的 Parse 或 ParseExact 方法,

這兩個方法都接受可選標記和格式提供者;ParseExact 還接受格式字串,

(P189)

DateTimeOffset 具有類似的構造方法,其區別是還需要指定一個 TimeSpan 型別的 UTC 偏移值,

TimeSpan 必須剛好是整數分鐘,否則函式會拋出一個例外,

DateTimeOffset 也有一些接受 Calendar 物件、 long 計數值的構造方法,以及接受字串的靜態的 Parse 和 ParseExact 方法,

還可以通過構造方法從現有的 DateTime 創建 DateTimeOffset ,

也可以通過隱式轉換創建, 從 DateTime 隱式轉換到 DateTimeOffset 是很簡單的,因為大多數的 .NET Framework 型別都支持 DateTime —— 而不是 DateTimeOffset ,

如果沒有指定偏移量,那么可以使用以下規則從 DateTime 值推斷出偏移值 :

1. 如果 DateTime 具有一個 UTC 的 DateTimeKind ,那么其偏移量為 0 ;

2. 如果 DateTime 具有一個 Local 或 Unspecified (默認) 的 DateTimeKind ,那么偏移量從當前的本地時區計算得到;

為了在其他方法中進行轉換,DateTimeOffset 提供了三個屬性,它們回傳 DateTime 型別的值 :

1. UtcDateTime 屬性會回傳一個 UTC 時間表示的 DateTime ;

2. LocalDateTime 屬性回傳一個以當前本地時區 (在需要時進行轉換) 表示的 DateTime ;

3. DateTime 屬性回傳一個以任意指定的時區表示的 DateTime ,以及一個 Unspecified 的 Kind ;

DateTime 和 DateTimeOffset 都具有一個靜態的 Now 屬性,它會回傳當前的日期和時間;

DateTime 也具有 Today 屬性,它回傳日期部分;

(P190)

靜態的 UtcNow 屬性會回傳以 UTC 表示的當前日期和時間,

所有這些方法的精度取決于作業系統,并且一般是在 10 ~ 20 毫秒內,

DateTime 和 DateTimeOffset 提供了回傳各種 日期 / 時間 的類似實體屬性,

DateTimeOffset 也有一個型別為 TimeSpan 的 Offset 屬性,

呼叫 DateTime 的 ToString 會將結果格式化為一個短日期 (全部是數字) ,后跟一個長時間 (包括秒) ,

(P191)

默認情況下,作業系統的控制面板決定日、月或年是否在前、是否使用前導零,以及是使用 12 小時還是 24 小時時間格式,

呼叫 DateTimeOffset 的 ToString 效果是一樣的,只是它同時回傳偏移值,

ToShortDateString 和 ToLongDateString 方法只回傳日期部分,

ToShortTimeString 和 ToLongTimeString 方法只回傳時間部分,

剛剛介紹的這四個方法實際上是四個不同的格式字串的快捷方式,ToString 多載后可以接受一個格式字串和提供者,這允許指定大量的選項,并且控制區域設定的應用方式,

靜態的 Parse 和 ParseExact 方法執行與 ToString 相反的操作,它們將一個字串轉換成一個 DateTime 或 DateTimeOffset ,Parse 方法多載后也可以接受格式提供者,

因為 DateTime 和 DateTimeOffset 是結構體,它們是不可為空的,當需要將它們設定為空時,可以使用以下兩種方法 :

1. 使用一個 Nullable 型別值;

2. 使用靜態域 DateTime.MinValue 或 DateTimeOffset.MinValue (這些型別的默認值) ;

使用一個可空值通常是最佳方法,因為編譯器會防止出現錯誤,DateTime.MinValue 對于兼容 C# 2.0 (引入了可空型別) 之前撰寫的代碼是很有用的,

(P192)

當比較兩個 DateTime 實體時,只有它們的計數值是可以比較的,它們的 DateTimeKinds 是被忽略的,

TimeZone 和 TimeZoneInfo 類提供了關于時區名稱、 UTC 偏移量和夏令時規則等資訊,

TimeZoneInfo 在兩者中較為強大,并且是 Framework 3.5 的新增特性,

這兩種型別的最大區別是 TimeZone 只能訪問當前的本地時區,而 TimeZoneInfo 則能夠訪問全世界的時區,而且,TimeZoneInfo 具有更豐富的 (雖然有時不宜使用) 基于規則的夏令時描述模型,

(P193)

靜態的 TimeZone.CurrentTimeZone 方法會基于當前的本地設定回傳一個 TimeZone 物件,

TimeZoneInfo 類采用類似的處理方式,TimeZoneInfo.Local 回傳當前的本地時區,

靜態的 GetSystemTimeZones 方法則回傳全世界所有的時區,

(P197)

格式化表示將物件轉換為一個字串;而決議表示將一個字串轉換為某種物件,

最簡單的格式化機制是 ToString 方法,它能夠為所有簡單的值型別產生有意義的輸出,對于反向轉換,這些型別都定義了靜態的 Parse 方法,

如果決議失敗,它會拋出一個 FormatException ,許多型別還定義了 TryParse 方法,如果轉換失敗,它會回傳 false ,而不是拋出一個例外,

(P198)

如果遇到錯誤,在例外處理代碼塊中呼叫 TryParse 是更快速且更好的處理方式,

使用格式提供者的方法是 IFormattable ,所有數字型別和 DateTime(Offset) 都實作了這個介面,

格式字串提供一些指令;而格式提供者則決定了這些指令是如何轉換的,

大多數型別都多載了 ToString 方法,可以省略 null 提供者,

(P199)

.NET Framework 定義了以下三種格式提供者 (它們都實作了 IFormatProvider) : NumberFormatInfo 、 DateTimeFormatInfo 、 CultureInfo ,

所有 enum 型別都可以格式化,但是它們沒有具體的 IFormatProvider 類,

在格式提供者的背景關系中,CultureInfo 作為其他兩個格式提供者的間接機制,回傳一個適合文化區域設定的 NumberFormatInfo 或 DateTimeFormatInfo ,

(P200)

組合格式字串可以包含組合變數替代符和格式字串,

Console 類本身多載了它的 Write 和 WriteLine 方法,以接受一個組合格式字串,

所有格式提供者都實作了 IFormatProvider 介面 ,

(P202)

標準格式字串決定數字型別或 DateTime / DateTimeOffset 集是如何轉換為字串的,格式字串有兩種 :

1. 標準格式字串 —— 可以使用標準格式字串是實作基本的控制,標準格式字串是由一個字母及其后面一個可選的數字 (它的作用由前面的字母決定) 組成;

2. 自定義格式字串 —— 可以使用自定義格式字串作為模板對每一個字符進行精細控制;

自定義格式字串與自定義格式提供者無關,

(P203)

如果不提供數字格式字串或者使用 null 或空字串,那么相當于使用不帶數字的 “G” 標準格式化字串,

每一種數字型別都定義了一個靜態的 Parse 方法,它接受 NumberStyles 引數,NumberStyles 是一個標記列舉值,可以判斷如何讀取轉換為數字型別的字串,

(P208)

.NET Framework 將以下型別稱為基本型別 :

1. bool 、 char 、 string 、 System.DateTime 和 System.DateTimeOffset ;

2. 所有 C# 數值型別;

靜態 Convert 類定義了將每一個基本型別轉換成其他基本型別的方法,可是,這些方法大多數都是無用的 : 它們或者拋出例外,或者是隱式轉換的冗余方法,

(P209)

所有基本型別都 (顯式地) 實作了 IConvertible ,它定義了轉換到其他基本型別的方法,在大多數情況中,每一種方法的實作都直接呼叫 Convert 類中的方法,在少數情況中,撰寫一個接受 IConvertible 型別的引數是很有用的,

允許在數字型別之間執行的隱式和顯式轉換,概括為 :

1. 隱式轉換只支持無值丟失的轉換;

2. 只有會出現值丟失的轉換才需要使用顯式轉換;

轉換是經過效率優化的,,因此它們將截斷不符合要求的資料,

Convert 的數值轉換方法采用圓整的方式,

Convert 采用銀行的圓整方式,將中間值轉換為偶整數 (這樣可以避免正負偏差) ,

To (整數型別) 方法隱含了一些多載方法,它們可以將數字轉換為其他進制,第二個引數指定了進制數,它可以是任何一種進制 (二、八、十或十六進制) ,

ChangeType 的缺點是無法指定一個格式字串或決議標記,

Convert 的 ToBase64String 方法能夠將一個位元組陣列轉換為 Base 64 ;FromBase64String 則執行相反操作,

(P211)

大多數基本型別都可以通過呼叫 BitConverter.GetBytes 轉換為位元組陣列,

應用程式的國際化包括兩個方面 : 全球化和本地化,

全球化注重于三個任務 (重要性由大到小) :

1. 保證程式在其他文化環境中運行時不會出錯;

2. 采用一種本地文化的格式化規則;

3. 設計程式,使之能夠從將來可能撰寫和部署的附屬程式集讀取與文化相關的資料和字串;

本地化表示為特定文化撰寫附屬程式集以結束最終任務,

(P213)

Round 方法能夠指定圓整的小數位數以及如何處理中間值 (遠離 0 ,或者使用銀行的圓整方式) ,

Floor 和 Ceiling 會圓整到最接近的整數 : Floor 總是向下圓整,而 Ceiling 則總是向上圓整 —— 即使是負數 ,

(P214)

BigInteger 結構體是 .NET Framework 新增的特殊數值型別,它位于 System.Numerics.dll 中新的 System.Numerics 命名空間,可以用于表示一個任意大的整數而不會丟失精度,

C# 并不提供 BigInteger 的原生支持,所以無法表示 BigInteger 值,然而,可以從任意整數型別隱式地轉換到 BigInteger ,

可以將一個 BigInteger 隱式地轉換為標準數值型別,也可以顯式地進行反向轉換,

BigInteger 多載了所有的算術運算子,以及比較、等式、求模 (%) 和負值運算子,

將一個數字存盤到一個 BigInteger 中而不是位元組陣列的優點是可以獲得值型別的語意,呼叫 ToByteArray 可以將一個 BigInteger 轉換回位元組陣列,

Complex 結構體是 Framework 4.0 新增的另一個特殊數值型別,用來表示用 double 型別的實數和虛數構成的復數,

要使用 Complex ,我們需要實體化這個結構體,指定實數和虛數值,

(P215)

Complex 結構體具有實數和虛數值的屬性,以及階和量級,

還可以通過指定量級和階來創建復數,

復數也多載了標準的算術運算子,

Complex 結構體具有一些支持更高級功能的靜態方法,其中包括 :

1. 三角函式;

2. 取對數與求冪;

3. 共軛;

Random 類能夠生成一個隨機 byte 、 integer 或 double 型別的偽亂數序列,

要使用 Random ,首先要實體化,可選擇提供一個種子來實體化亂數序列,使用相同的種子一定會產生相同序列的數字,當希望有可再現性時,是非常有用的,

如果不希望可再現性,那么可以不使用種子來創建 Random 而是使用當前系統時間來創建,

因為系統時鐘只有有限的粒度,創建時間間隔很小 (一般是 10ms 內) 的兩個 Random 將會產生相同序列的值,常用的方法是每次需要一個亂數時才實體化一個新的 Random 物件,而不是重用同一個物件,

呼叫 Next(n) 可以生成一個 0 至 n-1 之間的隨機整數,NextDouble 可以生成一個 0 至 1 之間的隨機 double 數值,NextBytes 會用亂數填充一個位元組陣列,

(P216)

System.Enum 的靜態實用方法主要是與轉換和獲取成員清單相關,

(P217)

每一種整型 (包括 ulong) 都可以轉換為十進制數而不會丟失值,

Enum.ToObject 能夠將一個整型值轉換為一個指定型別的 enum 實體,

(P218)

ToObject 已經多載,可以接受所有的整數型別和物件 (后者支持任何裝箱的整數型別) ,

Enum.Parse 可以將一個字串轉換為一個 enum ,它接受 enum 型別和一個包含多個成員的字串,

Enum.GetValues 回傳一個包含某特定 enum 型別的所有成員,

Enum.GetNames 執行相同的操作,但是回傳的是一個字串陣列,

在內部,CLR 通過反射 enum 型別的欄位實作 GetValues 和 GetNames ,其結果會被快取以提高效率,

列舉型別的語意很大程式上是由編譯器決定的,在 CLR 中,enum 實體 (未拆箱) 與它實際的整型值在運行時是沒有任何區別的,而且,在 CLR 中定義的 enum 僅僅是 System.Enum 的子型別,它的每個成員都是靜態的整型域,

(P219)

C# 會在呼叫 enum 實體的虛方法之前對它進行顯式裝箱,而且,當 enum 實體被裝箱后,它會獲得一個參考其 enum 型別的封裝,

Framework 4.0 提供了一組新的泛型類來保存不同型別的元素集,稱為元組,

每種元組都有名為 Item1 、 Item2 等的只讀屬性,分別對應一種型別引數,

可以通過它的構造方法實體化一個元組,或者通過靜態幫助方法 Tuple.Create ,后者使用的是泛型推斷方法,可以將這種方法與隱式型別轉換結合使用,

元組可以很方便地用來實作從一個方法回傳多個值或者創建值對集合,

元組的替代方法是使用物件陣列,然而,這種方法會影響靜態型別安全性,增加了值型別的 裝箱 / 開箱 開銷,并且需要作一些編譯器無法驗證的復雜轉換,

(P220)

元組是一些類 (也就是參考型別) ,

Guid 結構體表示一個全域唯一識別符號 : 一個隨機生成的 16 位值,幾乎可以肯定具有唯一性,Guid 在應用程式和資料庫中通常用作各種排序的鍵,

我們可以呼叫靜態的 Guid.NewGuid 方法創建一個新的隨機 Guid ,

ToByteArray 方法可以將一個 Guid 轉換為一個位元組陣列,

靜態的 Guid.Empty 屬性會回傳一個空的 Guid (全為零) ,通常用來替換 null ,

(P221)

相等有兩種型別 :

1. 值相等 —— 兩個值在某種意義上是相等的;

2. 參考相等 —— 兩個參考指向完全相同的物件;

默認情況下 :

1. 值型別采用的是值相等;

2. 參考型別采用的是參考相等;

事實上,值型別只能使用值相等形式進行比較 (除非已裝箱) ,

參考型別默認是采用參考相等的比較形式,

(P222)

有三種標準方法可以實作等值比較 :

1. == 和 != 運算子;

2. 物件的虛方法 Equals ;

3. IEquatable<T> 介面;

Equals 在 System.Object 中定義,所以所有型別都支持這個方法,

Equals 是在運行時根據物件的實際型別決議的,

對于結構體,Equals 會呼叫每個欄位的 Equals 執行結構比較,

(P223)

Equals 很適合用來比較兩個未知型別的物件,

object 類提供了一個靜態的幫助方法,它的名稱是 Equals ,與虛方法相同,但是不會有沖突,因為它接受兩個引數,

如果在處理編譯時未知型別物件,這是一種能夠避免 null 值例外的等值比較演算法,

(P224)

靜態方法 object.ReferenceEquals 可以實作參考等值比較,

另一種采用參考等值比較的方法是將值轉換為 object ,然后再使用 == 運算子,

呼叫 object.Equals 的結果是強制對值型別執行裝箱,這在對性能高度敏感的情況下是不太適合的,因為裝箱操作相對于實際比較操作的開銷還要高,C# 2.0 引入了一個解決辦法,那就是使用 IEquatable<T> 介面,

關鍵在于實作 IEquatable<T> 所回傳的結果與呼叫 object 的虛方法 Equals 是一樣的,但是執行速度會更快,大多數 .NET 基本型別都實作了 IEquatable<T> ,可以在泛型中使用 IEquatable<T> 作為一個約束,

(P225)

默認的等值比較操作有 :

1. 值型別采用的是值相等;

2. 參考型別采用的是參考相等;

此外 :

結構體的 Equals 方法默認采用的是結構值相等,

有時創建一個型別時多載這個行為是很有用的,有以下兩種情況我們需要這樣做 :

1. 修改相等的語意 —— 當 == 和 Equals 默認行為不符合要求的型別,并且這種行為一般人難以想象時,修改相等的語意是很有用的,

2. 提高結構體的等值比較的執行速度 —— 結構體的默認結構等值比較演算法相對較慢,通過多載 Equals 來實作這個程序可以將性能提高 20% ,多載 == 運算子和實作 IEquatable<T> 介面可以實作等值比較的拆箱,并且同樣能夠將比較速度提高 20% ,

(P226)

多載參考型別的等值語意并不能提高性能,參考等值比較的默認演算法已經非常快速,因為它只比較兩個 32 位或 64 位參考,

多載等值語意操作步驟總結 :

1. 多載 GetHashCode() 和 Equals() ;

2. (可選) 多載 != 和 == ;

3. (可選) 實作 IEquatable<T> ;

在 System.Object 中定義的 GetHashCode 對于散串列而言非常重要,所以每一種型別都具有一個散列碼,

參考型別和值型別都只有默認的 GetHashCode 實作,這意味著不需要多載這個方法 —— 除非多載了 Equals , (反之亦然,如果多載了 GetHashCode ,那么也必須多載 Equals) ,

下面是多載 object.GetHashCode 的其他規則 :

1. 它必須為 Equals 方法都回傳 true 的兩個物件回傳相同的值,因此, GetHashCode 和 Equals 必須同時多載;

2. 它不能拋出例外;

3. 如果重復呼叫相同物件,必須回傳相同的值 (除非物件改變) ;

(P227)

結構體的默認散列方法只是在每個欄位上執行按位異或操作,通常會比撰寫的演算法產生更多的重復碼,

類的默認 GetHashCode 實作基于一個內部物件標識,它在 CLR 當前實作中的每一個實體上都是唯一的,

object.Equals 的執行邏輯如下 :

1. 物件不能是 null (除非它是可空型別) ;

2. 相等是自反性的 (物件與其本身相等) ;

3. 相等是可交換的 (如果 a.Equals(b) ,那么 b.Equals(a)) ;

4. 相等時可傳遞的 (如果 a.Equals(b) 且 b.Equals(c) ,那么 a.Equals(c)) ;

5. 等值操作是可重復且可靠的 (它們不會拋出例外) ;

除了多載 Equals ,還可以選擇多載相等和不等運算子,這種多載幾乎都發生在結構體上,否則 == 和 != 運算子無法正確判斷型別,

對于類,與兩種方法可以處理 :

1. 保留 == 和 != ,這樣它們會應用參考相等;

2. 多載 Equals 同時多載 == 和 != ;

(P228)

為了保持完整性,在多載 Equals 時,最好也要實作 IEquatable<T> ,其結果應該總是與被多載物件 Equals 方法保持一致,如果自己撰寫 Equals 方法實作,那么實作 IEquatable<T> 并沒有任何的程式開銷,

(P229)

除了標準等值協議,C# 和 .NET 還定義了用于確定物件之間相對順序的協議,基本的協議包括 :

1. IComparable 介面 (IComparable 和 IComparable<T>) ;

2. > 和 < 運算子;

IComparable 介面可用于普通的排序演算法,

< 和 > 運算子比較特殊,它們大多數情況用于比較數字型別,因為它們是靜態決議的,所以可以轉換為高效的位元組碼,適用于一些密集型演算法,

.NET Framework 也通過 IComparer 介面實作了可插入的排序協議,

(P230)

CompareTo 方法按如下方式執行 :

1. 如果 a 在 b 之后,那么 a.CompareTo(b) 回傳一個正數;

2. 如果 a 與 b 位置相同,那么 a.CompareTo(b) 回傳 0 ;

3. 如果 a 在 b 之前,那么 a.CompareTo(b) 回傳一個負數;

(P231)

在多載 < 和 > 后,同時實作 IComparable 介面,這也是一種標準方法,但是反之不成立,事實上,大多數實作了 IComparable 的 .NET 型別都沒用多載 < 和 > ,與等值的處理方法不同的是,在等值中如果多載了 Equals ,一般也會多載 == ,

字串不支持 < 和 > 運算子,

【第07章】

(P234)

System.Diagnostics 中的 Process 類可以用于啟動一個新的行程,

Process 類也允許查詢計算機上運行的其他行程,并與之互動,

(P235)

.Net Framework 提供了標準的存盤和管理物件集合的型別集,其中包括可變大小串列、鏈表和排序或不排序字典以及陣列,在這些型別中,只有陣列屬于 C# 語言;其余的集合只是一些類,可以像使用其他類一樣進行實體化,

Framework 中的集合型別可以分成以下三類 :

1. 定義標準集合協議的介面;

2. 隨時可用的集合類 (串列、字典等) ;

3. 撰寫應用程式特有集合的基類;

集合命名空間有以下幾種 :

System.Collections —— 非泛型集合類和介面;
System.Collections.Specialized —— 強型別非泛型集合類;
System.Collections.Generic —— 泛型集合類和介面;
System.Collections.ObjectModel —— 自定義集合的委托和基類;
System.Collections.Concurrent —— 執行緒安全的集合;

(P236)

IEnumerator 介面定義了以向前方式遍歷或列舉集合元素的基本底層協議,

MoveNext 將當前元素或 “游標” 向前移動到下一個位置,如果集合沒有更多的元素,那么它會回傳 false ,Current 回傳當前位置的元素 (通常需要從 object 轉換為更具體的型別) ,在取出第一個元素之前,我們必須先呼叫 MoveNext —— 即使是空集合也支持這個操作,如果 Reset 方法實作了,那么它的作用就是將位置移回到起點,允許再一次遍歷集合, (通常是不需要呼叫 Reset 的,因為并非所有列舉器都支持這個方法) ,

IEnumerable 可以看作是 “IEnumerator 的提供者” ,它是集合類需要實作的最基礎介面,

(P237)

IEnumerable<T> 實作了 IDisposable ,它允許列舉器保存資源參考,并保證這些資源在列舉結束或者中途停止時能夠被釋放,foreach 陳述句能夠識別這個細節,

(P238)

using 陳述句保證清理操作的執行,

有時由于下面一個或多個原因而希望實作 IEnumerable 或 IEnumerable<T> :

1. 為了支持 foreach 陳述句;

2. 為了與任何使用標準集合的組件互動;

3. 作為一個更復雜集合介面實作的一部分;

4. 為了支持集合初始化器;

為了實作 IEnumerable / IEnumerable<T> ,必須提供一個列舉器,可以采用以下三個方法來實作 :

1. 如果這個類 “包裝” 了任何一個集合,那么就回傳所包裝集合的列舉器;

2. 使用 yield return 的迭代器;

3. 實體化 IEnumerator / IEnumerator<T> ;

還可以創建一個現有集合類的子類,Collection<T> 正是基于此目的而設計的,

回傳另一個集合的列舉器就是呼叫內部集合的 GetEnumerator ,然而,這種方法僅僅適合一些最簡單的情況,那就是內部集合的元素正好是所需要的型別,

更好的方法是使用 C# 的 yield return 陳述句撰寫迭代器,

迭代器是 C# 語言的一個特性,它能夠協助完成集合撰寫,與 foreach 陳述句協助完成集合遍歷的方式是一樣的,

迭代器會自動處理 IEnumerable 和 IEnumerator 或者它們的泛型類的實作,

注意, GetEnumerator 實際上不回傳一個列舉器,通過決議 yield return 陳述句,編譯器撰寫一個隱藏的列舉器類,然后重構 GetEnumerator 來實體化和回傳這個類,

迭代器很強大,也很簡單,并且是 LINQ 實作的基礎,

(P240)

因為 IEnumerable<T> 實作了 IEnumerable ,所以必須同時實作泛型和非泛型的 GetEnumerator ,

最后一種撰寫 GetEnumerator 的方法是撰寫一個直接實作 IEnumerator 的類,

(P241)

實作 Reset 方法不是必需的,相反,可以拋出一個 NotSupportedException ,

注意,第一次呼叫 MoveNext 會將位置移到串列的第一個 (而非第二個) 元素,

(P242)

IEnumerable<T> (和 IEnumerable ) —— 支持最少的功能 (只支持列舉) ,

ICollection<T> (和 ICollection ) —— 支持一般的功能 ,

IList<T> / IDictionary<K, V> 及其非泛型版本 —— 支持最多的功能 ,

大多數情況下不需要實作這些介面,幾乎在需要撰寫一個集合類的任何時候,都可以使用子類 Collection<T> 替代,

泛型和非泛型版本的差別很大,特別是對于 ICollection ,

因為泛型出現在后,而泛型介面是為了后面出現的泛型而開發的,

ICollection<T> 并沒有繼承 ICollection ;

IList<T> 也沒有繼承 IList ;

而且 IDictionary<TKey, TValue> 也同樣不繼承 IDictionary ,

當然,在有利的情況下,集合類本身通常是可以實作某個介面的兩個版本的,

.NET Framework 中并沒有一種統一使用集合 (collection) 和 串列 (list) 這兩個詞的方法,我們通常將 集合 (collection) 和 串列 (list) 這兩個術語看作在很多方面是同義的,只有在使用具體型別時例外,

ICollection<T> 是物件的可計數集合的標準介面,它提供了很多功能,包括確定集合大小 (Count) 、確定集合中是否存在某個元素 (Contains) 、將集合復制到一個陣列 (ToArray) 以及確定集合是否為只讀 (IsReadOnly) ,對于可寫集合,可能還需要對集合元素執行 Add 、 Remove 和 Clear 操作,而且,由于它繼承了 IEnumerable<T> ,所以也支持通過 foreach 陳述句進行遍歷,

(P243)

非泛型的 ICollection 具有與可計數集合類似的功能,但是它不支持修改串列或檢查元素成員的功能,

IList<T> 是標準的可按位置索引的介面,除了從 ICollection<T> 和 IEnumerable<T> 繼承的功能,它還提供了按位置 (通過一個索引器) 讀寫元素和按位置 插入 / 洗掉 元素的功能,

IndexOf 方法可以對串列執行線性搜索,如果未找到指定項,那么回傳 -1 ,

IList 非泛型版本具有更多的成員方法,因為它繼承了少量的 ICollection 成員方法,

(P244)

非泛型 IList 介面的 Add 方法回傳一個整數,這是最新添加元素的索引,相反,ICollection<T> 的 Add 方法的回傳型別為 void ,

通用的 List<T> 類是 IList<T> 和 IList 的典型表現,C# 陣列也同時實作了泛型和非泛型的 IList ,

為了與只讀的 Windows Runtime 集合實作互操作,Framework 4.5 引入了一個新的集合介面 IReadOnlyList<T> ,這個介面本身很有用,并且可以看作為 IList<T> 的縮減版本,它只包含串列只讀操作所需要的成員,

因為它的型別引數只用在輸出位置,所以它被標記為協變式 (covariant) ,

IReadOnlyList<T> 表示一個鏈表的只讀版本,它并不意味著底層實作也是只讀的,

IReadOnlyList<T> 與 Windows 運行時型別 IVectorView<T> 相對應,

(P245)

Array 類是所有一維和多維陣列的隱式基類,它是實作標準集合介面的最基本型別之一,

Array 類提供了型別統一性,所以常見的方法都適用于所有的陣列,而與它們宣告或實際的元素型別無關,

由于陣列是基本型別,所以 C# 提供了明確的宣告和初始化語法,

當使用 C# 語法宣告一個陣列時,CLR 會在內部將它轉化為 Array 的子類 —— 合成一個對應陣列維數和元素型別的偽型別,

CLR 也會特別處理陣列型別的創建,將它們分配到一塊連續的記憶體空間,因此陣列的索引非常高效,但是不允許在創建后修改陣列大小,

Array 實作了 IList<T> 的泛型與非泛型的集合介面,

Array 類實體也提供了一個靜態的 Resize 方法,但是它實際上是創建一個新陣列,然后將每一個元素復制到新陣列中,Resize 方法是很低效的,而且程式的陣列參考無法修改為新位置,

實作可變大小集合的最好方法是使用 List<T> 類,

(P246)

因為 Array 是一個類,所以無論陣列的元素是什么型別,陣列 (本身) 總是參考型別,

兩個不同的陣列在等值比較中總是不相等的 —— 除非使用自定義的等值比較,

Framework 4.0 提供了一種用于比較陣列或元組元素的比較方式,可以通過 StructuralComparisons 型別進行訪問,

陣列可以通過 Clone 方法進行復制,然而,這是一個淺克隆,表示只有陣列本身表示的記憶體會被復制,如果陣列包含的是值型別的物件,那么這些值會被復制類;如果陣列包含的是參考型別的物件,那么只有參考被復制,

如果要進行深度復制即復制參考型別子物件,必須遍歷整個陣列,然后手動克隆每個元素,相同的規則也適用于其他 .NET 集合型別,

CLR 不允許任何物件 (包括陣列) 在大小上超過 2GB (無論是運行在 32 位或是 64 位環境上) ,

(P247)

你可能會以為 Array 類的許多方法是實體方法,但是實際上它們是靜態方法,這是一個奇怪的設計方法,意味著在尋找 Array 方法時,應該同時查看靜態方法和實體方法,

最簡單的創建和索引陣列的方法是使用 C# 的語言構造,

此外,可以通過呼叫 Arrray.CreateInstance 動態實體化一個陣列,可以在運行時指定元素型別和維數以及為非零開始索引的陣列指定下界,非零開始索引的陣列不符合 CLS (Common Language Specification ,公共語言規范) ,

靜態的 GetValue 和 SetValue 方法訪問動態創建的陣列的元素 (它們也支持普通陣列的元素訪問) ,

動態創建的從零開始索引的陣列可以轉換為一種型別匹配或兼容 (兼容標準陣列變化規則) 的 C# 陣列,

為什么不使用 object[] 作為統一的陣列型別,而要使用 Array 類呢?原因就是 object[] 既不兼容多維陣列,也不兼容值型別以及非零開始索引的陣列,

GetValue 和 SetValue 也支持編譯器創建的陣列,并且它們對于撰寫能夠處理任意型別和任意維數陣列的方法是很有用的,

(P248)

如果元素與陣列型別不一致,SetValue 方法會拋出一個例外,

當實體化陣列時,無論是通過語言語法還是 Array.CreateInstance ,陣列元素都會自動初始化,對于參考型別元素的陣列,這意味著寫入 null 值;對于值型別元素的陣列,這意味著呼叫值型別的默認建構式 (實際上是成員的 “歸零” 操作),

陣列可以通過 foreach 陳述句進行列舉,

也可以使用靜態的 Array.ForEach 方法進行列舉,

(P249)

GetLength 和 GetLongLength 會回傳一個指定維度的長度 (0 表示一維陣列),而 Length 和 LongLength 回傳陣列的元素總數 (包括所有維數) ,

GetLowerBound 和 GetUpperBound 在處理非零開始索引的陣列時是很有用的,GetUpperBound 回傳的結果與任意維度的 GetLowerBound 和 GetLength 相加的結果是相同的,

(P250)

Array.Sort 要求陣列中的元素實作 IComparable ,這意味著 C# 的最基本型別都可以進行排序,

如果元素是不可比較的,或者希望重寫默認的順序比較,那么必須給 Sort 提供一個自定義的比較提供者,用來判斷兩個元素的相對位置,可以采用以下方法 :

1. 通過一個實作 IComparer / IComparer<T> 的幫助物件;

2. 通過一個 Comparison 委托 : public delegate int Comparison<T> (T x, T y) ;

Comparison 委托采用與 IComparer<T>.CompareTo 相同的語意,

(P251)

作為 Sort 的替代方法,可以使用 LINQ 的 OrderBy 和 ThenBy 運算子,與 Array.Sort 不同的是,LINQ 運算子不會修改原始陣列,而是將排序結果保存在一個新的 IEnumerable<T> 序列中,

Array 有 4 個方法可以執行淺拷貝操作 : Clone 、 CopyTo 、 Copy 和 ConstrainedCopy ,前兩個方法都是實體方法;后兩個方法是靜態方法,

Clone —— 方法回傳一個全新 (淺拷貝) 的陣列;

CopyTo 和 Copy —— 方法復制陣列的若干連續元素;

ConstrainedCopy —— 執行一個原子操作 : 如果所有請求的元素都無法成功復制,那么操作會回滾;

Array 還有一個 AsReadOnly 方法,它會回傳一個包裝器,可以防止元素被重新賦值,

(P252)

System.Linq 命名空間包含另外一些適合用于執行陣列轉換的擴展方法,這些方法會回傳一個 IEnumerable<T> ,它可以通過 Enumerable 的 ToArray 方法轉換回一個陣列,

在靈活性和性能方面,泛型類更具優勢,而它們的非泛型冗余實作則是為了實作向后兼容,

泛型 List<T> 和非泛型 ArrayList 類提供了一種動態調整大小的物件陣列實作,它們是集合類中使用最廣泛的類, ArrayList 實作了 IList ,而 List<T> 同時實作了 IList 和 IList<T> ,與陣列不同,所有介面都是公開實作的,

在內部,List<T> 和 ArrayList 都維護了一個物件陣列,并在超出容量時替換為一個更大的陣列,添加元素是很高效的 (因為陣列末尾通常還有空閑存盤位置) ,但是插入元素的速度會慢一些 (因為插入位置之后的所有元素都必須向后移動才能留出插入空間) ,與陣列一樣,如果對已排序串列執行 BinarySearch 方法,那么查找是很高效的,但是其他情況效率就不高,因為查找時必須檢查每一個元素,

如果 T 是一種值型別,那么 List<T> 的速度會比 ArrayList 快好幾倍,因為 List<T> 不需要元素執行裝箱和開箱操作,

List<T> 和 ArrayList 具有可以接受已有元素集合的建構式,它們會將已有集合的每一個元素復制到新的 List<T> 或 ArrayList 中,

(P254)

非泛型 ArrayList 類主要用于向后兼容 Framework 1.x 代碼,

ArrayList 的功能與 List<object> 型別相似,當需要一個包含不共享任何相同基類的混合型別元素時,這兩種型別是很有用的,在這種情況下,如果需要使用反射機制處理串列,那么選擇使用 ArrayList 更具優勢,相比于 List<object> ,反射機制更容易處理非泛型的 ArrayList ,

如果定義 System.Linq 命名空間,那么可以通過先呼叫 Cast 再呼叫 ToList 的方式將一個 ArrayList 轉換為一個泛型 List ,

Cast 和 ToList 是 System.Linq.Enumerable 的擴展方法,是從 .NET Framework 3.5 開始支持的,

LinkedList<T> 是一個泛型的雙向鏈表,雙向鏈表是一系列互相參考的節點,其中每個節點都參考前一個節點、后一個節點及實際存盤資料的元素,它的主要優點是元素總是能夠高效地插入到鏈表的任意位置,因為插入節點只需要創建一個新節點,然后修改參考值,然而,查找插入節點的位置可能減慢執行速度,因為鏈表本身沒有直接索引的內在機制;我們必須遍歷每一個節點,并且無法執行二叉查找,

(P255)

LinkedList<T> 實作了 IEnumerable<T> 和 ICollection<T> 及其非泛型版本,但是沒有實作 IList<T> ,因為它不支持根據索引進行訪問,

(P256)

Queue<T> 和 Queue 是一種先進先出 (FIFO) 的資料結構,它們提供了 Enqueue (將一個元素添加到佇列末尾) 和 Dequeue (取出并洗掉佇列的第一個元素) 方法,它們還包括一個只回傳而不洗掉佇列第一個元素的 Peek 方法,以及一個 Count 屬性 (可用來檢查出列前的元素個數) ,

雖然佇列是可列舉的,但是它們都沒有實作 IList<T> / IList ,因為不能夠直接通過索引訪問它的成員,

佇列內部是使用一個可根據需要調整大小的陣列來操作的,這與一般的 List 類很類似,佇列具有一個直接指向頭和尾元素的索引,因此,入列和出列操作是及其快速的 (除非內部的大小需要調整) ,

(P257)

Stack<T> 和 Stack 是后進先出 (LIFO) 的資料結構,它們提供了 Push (添加一個元素到堆疊的頂部) 和 Pop (從堆疊頂部取出并洗掉一個元素) 方法,它們還提供了一個只讀而不洗掉元素的 Peek 方法,以及 Count 屬性和用于匯出資料以實作隨機訪問的 ToArray 方法,

堆疊內部也是使用一個可根據需要調整大小的陣列來操作,這一點和 Queue<T> 與 List<T> 類似,

BitArray 是一個保存壓縮 bool 值的可動態調整大小的集合,它具有比簡單的 bool 陣列和 bool 泛型 List 更高的記憶體使用效率,因為它的每個值只占用一位,而 bool 型別的每個值占用一個位元組,

(P258)

HashSet<T> 和 SortedSet<T> 分別是 Framework 3.5 和 4.0 新增加的泛型集合,這兩個類都具有以下特點 :

1. 它們的 Contains 方法都使用基于散列的查找而實作快速執行;

2. 它們都不保存重復元素,并且都忽略添加重復值的請求;

3. 無法根據位置訪問元素;

SortedSet<T> 按一定順序保存元素,而 HashSet<T> 則不是,

這些型別的共同點是由介面 ISet<T> 提供的,

HashSet<T> 是通過使用只存盤鍵的散串列實作的;而 SortedSet<T> 則是通過一個 紅 / 黑 樹實作的,

兩個集合都實作了 ICollection<T> 介面,

因為 HashSet<T> 和 SortedSet<T> 實作了 IEnumerable<T> 介面,所以可以將另一種集合作為任意集合操作方法的引數,

SortedSet<T> 的建構式還接受一個可選的 IComparer<T> 引數 (而非一個等值比較器) ,

(P259)

字典是一種所包含元素均為 鍵 / 值 對的集合,字典通常都用來執行串列查找和排序,

Framework 通過介面 IDictionary 和 IDictionary<TKey, TValue> 及一組通用的字典類定義了一個標準字典協議,這些類在以下方面有區別 :

1. 元素是否按有序序列存盤;

2. 元素是否按位置 (索引) 或按鍵訪問;

3. 類是泛型還是非泛型的;

4. 集合變大時的性能;

(P260)

IDictionary<TKey, TValue> 定義了所有基于 鍵 / 值 的集合的標準協議,它擴展了 ICollection<T> ,增加了一些基于任意型別的鍵訪問元素的方法和屬性,

(P261)

從 Framework 4.5 開始,還出現了一個介面 IReadOnlyDictionary<TKey, TValue> ,它定義了字典成員的只讀子集,它與 Windows Runtime 型別 IMapView<K, V> 相對應,當時也是因為相同原因而引入的,

重復的鍵在所有字典實作中都是禁止的,所以用相同的鍵呼叫兩次 Add 會拋出一個例外,

直接通過一個 IDictionary<TKey, TValue> 進行列舉會回傳一個 KeyValuePair 結構體序列,

非泛型的 IDictionary 介面在原理上與 IDictionary<TKey, TValue> 相同,但是存在以下兩個重要的功能區別 :

1. 通過索引器查找一個不存在的鍵會回傳 null (而不是拋出一個例外) ;

2. 使用 Contains 而非 ContainsKey 來檢測成員是否存在 ;

列舉一個非泛型 IDictionary 會回傳一個 DictionaryEntry 結構體序列,

泛型 Dictionary (和 List<T> 集合一樣) 是使用最廣泛的集合之一,它使用一個散串列結構來存盤鍵和值,而且快速、高效,

Dictionary<TKey, TValue> 的非泛型版本是 Hashtable ;Framework 中不存在名為 Dictionary 的非泛型類,當我們提到 Dictionary 時,指的是泛型的 Dictionary<TKey, TValue> 類,

Dictionary 同時實作了泛型和非泛型的 IDictionary 介面,而泛型 IDictionary 是公開的介面,

事實上, Dictionary 是泛型 IDictionary 的一個標準實作,

(P262)

Dictionary 和 Hashtable 的缺點是元素是無序的,而且,添加元素時不保存原始順序,此外,所有字典型別都不允許出現重復值,

(P263)

OrderedDictionary 是一種非泛型字典,它能夠保存添加元素的原始順序,通過使用 OrderedDictionary ,既可以根據索引訪問元素,也可以根據鍵進行訪問,

OrderedDictionary 并不是一個有序的字典,

OrderedDictionary 是 Hashtable 和 ArrayList 的組合,

這個類是在 .NET 2.0 中引入的,特殊的是,它沒有泛型版本,

ListDictionary 和 HybridDictionary 這兩個類都只有非泛型版本,

Framework 只支持兩種在內部結構中將內容根據鍵進行排序的字典 :

1. SortedDictionary<TKey, TValue> ;

2. SortedList <TKey, TValue> (SortedList 是具有相同功能的非泛型版本) ;

(P265)

Collection<T> 類是一個可定制的 List<T> 包裝類,

(P267)

CollectionBase 是 Framework 1.0 引入的 Collection<T> 的非泛型版本,它提供了大多數與 Collection<T> 相似的特性,但是使用方式不太靈活,

KeyedCollection<TKey, TItem> 是 Collection<Item> 的子類,它增加也刪去了一些功能,它增加的功能是按鍵訪問元素,這與字典很相似,刪去的功能是委托自己的內部串列,

KeyedCollection<TKey, TItem> 通常看作是實作了按鍵進行快速查找的 Collection<TItem> ,

(P269)

KeyedCollection 的非泛型版本稱為 DictionaryBase ,

DictionaryBase 存在的目的就是為了向后兼容,

ReadOnlyCollection<T> 是一個包裝器,或者稱為委托,它提供了集合的一種只讀視圖,它的用途是允許一個類公開地顯示集合的只讀訪問,但是同時這個類仍然可以在內部進行修改,

【第08章】

(P277)

LINQ 是 Language Integrated Query 的簡寫,它可以被視為一組語言和框架特性的集合,我們可以使用 LINQ 對本地物件和遠程資料源進行結構化的型別安全的查詢操作,

在 C# 3.0 和 Framework 3.5 中引入了 LINQ ,

LINQ 可用于查詢任何實作了 IEnumerable<T> 介面的集合型別,

LINQ 具有編譯時的型別檢查及動態查詢組合這兩大優點,

LINQ 中所有核心型別都包含在 System.Linq 和 System.Linq.Expressions 這兩個命名空間中,

LINQ 資料源的基本組成部分是序列和元素,在這里,序列是指任何實作了 IEnumerable<T> 介面的物件,其中的每一項則稱為一個元素,

查詢運算子是 LINQ 中用于轉換序列的方法,通常,查詢運算子可接收一個輸入序列,并將其轉換為一個輸出序列,在 System.Linq 命名空間的 Enumerable 類中定義了約 40 種查詢運算子,這些運算子都是以靜態擴展方法的形式來實作的,稱為標準查詢運算子,

我們把對本地序列進行的查詢操作稱為本地查詢或者是 LINQ 到物件查詢,

LINQ 還支持對那些從遠程資料源中動態獲取的序列進行查詢,這些序列需要實作 IQueryable<T> 介面,而在 Queryable 類中則有一組相應的標準查詢運算子對其進行支持,

(P278)

一個查詢可以理解為一個使用查詢運算子對所操作的序列進行轉換的運算式,

由于標準查詢運算子都是以靜態擴展方法的方式來實作的,因此我們可以像使用物件的實體方法那樣直接使用,

大多數查詢運算子都接受一個 Lambda 運算式作為引數,

Lambda 運算式用于對查詢進行格式化,

(P279)

運算子流語法和查詢運算式語法是兩種互補的 LINQ 表達方法,

運算子流是最基本同時也是最靈活的書寫 LINQ 運算式的方式,

如果想創建更復雜的查詢運算式,只需在前面的運算式后面添加新的查詢運算子,

(P280)

查詢運算子絕不會修改輸入序列,相反,它會回傳一個新序列,這種設計是符合函式式編程規范的, LINQ 的思想實際上就起源于函式式編程,

(P281)

每個查詢運算子對應著一個擴展方法,

(P282)

回傳一個 bool 值的運算式我們稱之為 “斷言” ,

查詢運算子的 Lambda 運算式針對的是集合中的每個元素,而不是集合整體,

標準的查詢運算子使用了一個泛型 Func 委托, Func 是 System.Linq 命名空間中一組通用的泛型委托,它的作用是保證 Func 中的引數順序和 Lambda 運算式中的引數順序一致,

(P283)

標準的查詢運算子使用下面這些泛型 :

1. TSource —— 輸入集合的元素型別;

2. TResult —— 輸出集合的元素型別 (不同于 TSource) ;

3. TKey —— 在排序、分組或者連接操作中所用的鍵 ;

這里的 TSource 由輸入集合的元素型別決定,而 TResult 和 TKey 則由我們給出的 Lambda 運算式指定,

Lambda 運算式可以指定輸出序列的型別,也就是說 Select 運算子可以根據 Lambda 運算式中的定義將輸入型別轉化成輸出型別,

Where 查詢運算子的內部操作比 Select 查詢運算子要簡單一些,因為它只篩選集合,不對集合中的元素進行型別轉換,因此不需要進行型別推斷,

Func<TSource, TKey> 將每個輸入元素關聯到一個排序鍵 TKey ,TKey 的型別也是由 Lambda 運算式中推測出來的,但它的類與同輸入型別、輸出型別是沒有關系的,三者是獨立的,型別可以相同也可以不同,

(P284)

實際上我們可以使用傳統的方式直接呼叫 Enumerable 中的各種方法來實作查詢運算子的功能,此時在查詢程序可以不使用 Lambda 運算式,這種直接呼叫的方式在對本地集合進行查詢時非常好用,尤其是在 LINQ to XML 這種操作中應用最為方便,

傳統呼叫方式并不適合對 IQueryable<T> 型別集合的查詢,最典型的就是對資料庫的查詢,因為在對 IQueryable<T> 型別資料進行查詢時,Queryable 類中的運算子需要 Lambda 運算式來生成完整的查詢運算式樹,沒有 Lambda 運算式,這個運算式樹將不能生成,

LINQ 中集成了對集合的排序功能,這種內置的排序對整個 LINQ 體系來說有重要意義,因為一些查詢操作直接依賴于這種排序,

Take 運算子 —— 會輸出集合中前 x 個元素,這個 x 以引數的形式指定;

Skip 運算子 —— 會跳過集合中的前 x 個元素,輸出其余元素;

Reverse 運算子 —— 則會將集合中的所有元素反轉,也就是按照元素當前順序的逆序排列;

Where 和 Select 這兩個查詢運算子在執行時,會將集合中元素按照原有的順序進行輸出,實際上,在 LINQ 中,除非有必要,否則各個查詢運算子都不會改變集合中元素的排序方式,

(P285)

Union 運算子會將結果集合中相同的元素去掉;

(P286)

查詢運算式一般以 from 子句開始,最后以 select 或者 group 子句結束,

(P287)

查詢運算式中的所有邏輯都可以用運算子流語法來書寫,

緊跟在 from 關鍵字之后的識別符號實際上是一個范圍變數,范圍變數指向當前序列中將要進行操作的元素,

在每個子查詢的 Lambda 運算式中,范圍變數都會被重新定義,

要定義存盤中間結果的變數,需要使用下面幾個子句 : let 、 into 、一個新的 from 子句、 join ,

(P288)

查詢運算式語法和運算子流語法各有優勢,

在包含以下運算子的查詢操作中,使用查詢運算式語法更加方便 :

1. 在查詢中使用 let 子句匯入新的查詢變數;

2. 在查詢中用到 SelectMany 、 Join 或者 GroupJoin 這些運算子;

對于只包含 Where 、 OrderBy 或者 Select 的查詢陳述句,這兩種查詢方式都可以,

一般來說,查詢運算式語法由單個的運算子組成,結構比較清晰;而運算子流語法寫出的代碼相對簡潔,

在不含以下運算子的查詢中,選用運算子流語法進行查詢會更加方便 : Where 、 Select 、 SelectMany 、 OrderBy 、 ThenBy 、 OrderByDescending 、 ThenByDescending 、 GroupBy 、 Join 、 GroupJoin ,

如果一個查詢運算子沒有適合的查詢語法,可以混合使用兩種查詢方式來得到最終結果,這樣做的唯一限制是,在整個查詢中,每個查詢運算式的表達必須是完整的 (必須由 from 子句開始,由 select 或者 group 子句結束) ,

(P289)

在比較復雜的查詢中,混合使用兩種查詢語法進行查詢的方式非常高效,

有時候,即使混合使用了兩種查詢語法,也沒有寫出真正簡練的 LINQ 查詢,但注意不要因此養成只使用一種查詢語法的習慣,如果習慣只使用一種語法形式的,在遇到復雜查詢情況時,很難找到一種真正高效的方式去解決問題,

在 LINQ 中,另一個很重要的特性是延遲執行,也可以說是延遲加載,它是指查詢操作并不是在查詢運算子定義的時候執行,而是在真正使用集合中的資料時才執行,

絕大部分標準的 LINQ 查詢運算子都具有延遲加載這種特性,當然也有例外,以下是幾個例外的運算子 :

1. 那些回傳單個元素或者回傳一個數值的運算子;

2. 轉換運算子 : ToArray 、 ToList 、 ToDictionary 、 ToLookup ;

以上這些運算子都會觸發 LINQ 陳述句立即執行,因為它們的回傳值型別不支持延遲加載,

(P290)

在 LINQ 中,延遲加載特性有很重要的意義,這種設計將查詢的創建和查詢的執行進行了解耦,這使得我們可以將查詢分成多個步驟來創建,有利于查詢運算式的書寫,而且在執行的時候按照一個完整的結構去查詢,減少了對集合的查詢次數,這種特性在對資料庫的查詢中尤為重要,

子查詢中的運算式有額外的延遲加載限制,無論是聚合運算子還是轉換運算子,如果出現在子查詢中,它們都會被強制地進行延遲加載,

(P292)

LINQ 查詢運算子之所以有延遲加載功能,是因為每個運算子的回傳值不是一個一般的陣列或者集合,而是一個經過封裝的序列,這種序列通常情況下并不直接存盤資料元素,它封裝并使用運行時傳遞給它的集合,元素也由其他集合來存盤它實際上只是維護自己與資料集合的一種依賴關系,當有查詢請求時,再到它依賴的序列中進行真正的查詢,

查詢運算子實際上是封裝一系列的轉換函式,這種轉換函式可以將與之關聯的資料集轉換為各種形式的序列,如果輸出集合不需要轉換的話,那么就不用執行查詢運算子封裝的轉換操作,這個時候查詢運算子實際上就是一個委托,進行資料轉發而已,

(P293)

如果使用運算子流語法對集合進行查詢,會創建多個層次的封裝集合,

在使用 LINQ 陳述句的回傳集合時,實際是在原始的輸入集合中進行查詢,只不過在進入原始集合之前,會經過上面這些封裝類的處理,在不同層次的封裝類中,系統都會對查詢做相應的修改,這使得 LINQ 陳述句使用的各種查詢條件會被反映到最終的查詢結果中,

(P294)

如果在 LINQ 查詢陳述句的最后加上 ToList 方法,會強制 LINQ 陳述句立刻執行,查詢結果會被保存到一個 List 型別的集合中,

LINQ 的延遲加載特性有這樣一種功能 : 不論查詢陳述句是連續書寫的還是分多個步驟完成的,在執行之前,都會被組合成一個完整的物件模型,而且兩種書寫方式所產生的物件模型是一樣的,

LINQ 查詢是一個低效率的流水線,

(P295)

LINQ 使用的是需求驅動的模型,先請求再有資料,

在 LINQ 中,所謂子查詢就是包含在另一個查詢的 Lambda 運算式中的查詢陳述句,

一個子查詢實際上就是一個獨立的 C# 運算式,可以是 LINQ 運算式,也可以是普通的邏輯判斷,所以只要是符合 C# 語法規則的內容,都可以放在 Lambda 運算式的右側作為子查詢來使用,也就是說,子查詢的使用規則是由 Lambda 運算式的規則所決定的,

“子查詢” 這個詞,在通常意義下,概念非常寬泛,我們只關注 LINQ 下的子查詢,在運算子流語法中,子查詢是指包含在 Lambda 運算式中的查詢陳述句,在查詢運算式中,只要包含在其他查詢陳述句中的查詢,都是子查詢,但是 from 子句除外,

子查詢一般有兩個作用 : 一個是為父查詢確定查詢范圍,一般是一個較小的查詢范圍,另一個作用是為外層查詢的 Lambda 運算式提供引數,

(P296)

子查詢在什么時候執行完全是由外部查詢決定的,當外部查詢開始執行時,子查詢也同時執行,它們是同步的,在整個查詢中,子查詢的執行結果被作為父查詢的某個組成部分,我們可以認為查詢的開始命令是從外向內傳遞的,對本地集合的查詢嚴格按照這種由外向內的順序進行;但對資料庫的查詢,則沒有那么嚴格,只是原則上按照這種方式進行,

另一種理解方式是,子查詢會在需要回傳查詢結果時執行,那什么時候需要子查詢回傳查詢結果決定于外部查詢什么時候被執行,

(P297)

在執行本地查詢時,單獨書寫子查詢是一種常用的查詢方式,但是當子查詢中的資料和外部查詢有緊密關聯的時候,即內部資料需要用到外部資料的值時,這種方式不適合,最好寫成一個運算式,

(P298)

在子查詢中使用單個元素或者聚合函式的時候,整個 LINQ 查詢陳述句并不會被強制執行,外部查詢還是以延遲加載的方式執行,這是因為子查詢是被間接執行的,在本地集合查詢中,它通過委托的驅動來執行;而在遠程資料源的查詢中,它通過運算式樹的方式執行,

如果 Select 陳述句中已經包含了子查詢,在這種情況下如果是本地查詢,那么相當于將源序列重新封裝到一個新的序列中,集合中的每個元素都是以延遲加載的方式執行的,

書寫復雜的 LINQ 查詢運算式的三種方式 :

1. 遞增式的書寫方式;

2. 使用 into 關鍵字;

3. 包裝查詢陳述句;

實際上無論用何種書寫方式,在運行時,LINQ 查詢運算式都會被編譯成相同的查詢陳述句來運行,

在使用多個查詢條件進行查詢的時候,這種遞增式的書寫方式比較實用,

(P299)

根據背景關系的不同, into 關鍵字在查詢運算式中有兩種完全不同的功能,這里首先介紹如何使用 into 關鍵字延長查詢 (另一種是和 GroupJoin 配合使用) ,

在 LINQ 查詢中,一般會用到集合的映射,也就是在 Select 方法中將查詢結果直接組裝成新的集合,這種映射一般在查詢的最后執行,但是如果在映射之后還想對新集合執行查詢的話,就可以使用 into 關鍵字來完成,

(P300)

注意,into 關鍵字只能出現在 select 和 group 關鍵字之后,into 會重新創建一個新的查詢,在新的查詢中,我們可以再次使用 where 、 orderby 、 select 關鍵字,

into 關鍵字的作用就是在原來的查詢中重新創建一次新的查詢,在執行前,這種帶 into 的查詢運算式會被編譯成運算子流的查詢陳述句,因此使用 into 運算子并不會帶來性能上的損失,

包含了多個層次的查詢運算式,在語意和執行上都和遞增式的 LINQ 查詢陳述句相同,它們本質上沒有區別,唯一的區別就是查詢關鍵字的使用順序,

在多層次查詢中,內部查詢是在傳遞帶之前執行的,而子查詢則是傳送帶上的一部分,它會隨著整個傳送帶的運行而執行,

(P302)

所謂匿名型別指的是沒有顯式定義過的型別,在查詢程序中,可以使用這種型別來封裝查詢結果,實際上這個類并不是沒有定義,只是不用我們自己定義,編譯器會自動定義這個型別,

要在 C# 代碼中定義一種編譯時才能確定的型別,唯一的選擇是使用 var 關鍵字,此時 var 關鍵字就不僅僅是為了便于書寫,而是不得不這么寫,因為我們不知道匿名型別的名字,

(P303)

使用 let 關鍵字,可以在查詢中定義一個新的臨時變數來存放某些步驟的查詢結果,

編譯器在編譯 let 關鍵字的時候,會把它翻譯成一個匿名型別,這個匿名型別中包含了之前的范圍變數 n 和一個新的運算式變數,也就是說,編譯器將 n 翻譯成了前面的匿名型別查詢,

let 還有以下兩個優點 :

1. 保留了前面查詢中的范圍變數;

2. 在一個查詢中可以重復使用它定義的變數;

在 LINQ 查詢中,在 where 關鍵字之前或之后可以使用任意多個 let 關鍵字,后面的 let 關鍵字會使用前面 let 關鍵字的回傳型別,顯然,let 關鍵字會在每一次使用時重新組成結果集,

let 關鍵字一般不用來回傳數值型別的結果,更多使用在子查詢中,

LINQ 包含兩種查詢 : 對本地集合的本地查詢以及對遠程資料的解釋型查詢,

對本地集合的查詢,這種查詢呼叫 IEnumerable<> 介面中定義的 Enumerable 方法實作了介面中所有的方法來完成具體的查詢,

在解釋型的查詢中,所有的查詢操作都是通過 IQueryable<T> 介面中的方法完成的,具體的方法實作是在 Queryable 類中,在這種查詢中,LINQ 陳述句不會被編譯成 .NET Framework 中間語言 (IL),而會在運行時被解釋成查詢運算式樹來執行,

(P304)

實際上,可以使用 Enumerable 中的方法來查詢 IQueryable<T> 型別的資料源,但會遇到一個問題,那就是查詢的時候,遠端的資料源必須被加載到本地記憶體中,然后以本地資料源的方式進行處理,可以想象,這種查詢的效率非常低,每次都需要讀取大量的資料,在本地進行篩選,這正是創建解釋型查詢的原因,

在 .NET Framework 中有兩個類都實作了 IQueryable<T> 介面,這兩個類用于實作兩種不同的查詢 :

1. LINQ to SQL;

2. Entity Framework (EF);

這兩種 LINQ-to-db 的查詢技術實際上非常相似,

在對本地資料源的查詢中,也可以使用 IQueryable<T> 介面中的方法進行查詢,只要在本地集合的最后使用一個 AsQueryable 方法即可,

IQueryable<T> 實際上是對 IEnumerable<T> 方法的擴展,

(P306)

查詢運算式樹是 System.Linq.Expression 命名空間下的一種物件模型,這種物件是在運行時被解釋運行的 (這也是為什么 LINQ to SQL 和 EF 支持延遲加載),

解釋型的查詢和本地資料查詢的本質不同在于它們的執行方式,在遍歷解釋型的集合時,整個 LINQ 查詢陳述句會被編譯成一個完整的查詢運算式樹來加以執行,

(P307)

Entity Framework 也需要類似的標簽,但是除了這些之外,他還需要一個額外的 XML 檔案 Entity Data Model (EDM),在這個檔案中定義了資料表和物體類的對應關系,

LINQ to SQL 和 EF 中可能定義了 30 種查詢方式,但是在 SQL Server 的 SQL 查詢中只有 10 種查詢方式,而最終 LINQ 查詢運算式要被翻譯成 SQL 來執行,那么只能在 10 種查詢方法中選一種來使用,如果在 LINQ 使用了一個功能很強大的運算子,但是在 SQL 中卻沒有相同功能的運算子,那么 LINQ 中的這個運算子就會被翻譯成其他的 SQL 陳述句來完成這項功能,

一個 LINQ 查詢中可以同時使用解釋型查詢運算子和本地查詢運算子,應用的典型方式就是把本地查詢操作放在外層,將解釋型的查詢操作放在內層,在執行查詢的時候,解釋型的操作先執行,回傳一個結果集合給外層的本地查詢使用,這種查詢模式經常用于 LINQ 對資料庫的查詢操作,

查詢運算子絕不會修改輸入序列,相反,它會回傳一個新序列,這種設計是符合函式式編程規范的,LINQ 的思想實際上就起源于函式式編程,

(P309)

兩種方式可以間接地呼叫 AsEnumerable 方法,那就是 ToArray 方法和 ToList 方法,使用 AsEnumerable 方法有下面兩點好處,一是這個方法不會強制查詢立即執行,但是如果希望查詢立即執行的話,就要使用另外兩個方法了;二是它不會創建本地的存盤結構,因此它會比較節省資源,

當查詢邏輯從資料庫移到本地會降低查詢的性能,特別是當查詢的資料量比較大的時候,效率損失更加嚴重,同樣針對上面這個示例,有一個更有效 (同時也更復雜) 的方式來完成上面的查詢,那就是使用 SQL CLR 在資料庫端實作正則運算式的查詢,

(P310)

LINQ to SQL 和 EF 都是用 LINQ 來實作的物件的映射工具,它們之間的不同在于映射的方式,我們知道,在資料庫查詢中,映射的一端是資料庫表,LINQ to SQL 可以將資料庫表結構映射成物件,然后供呼叫者使用,這種映射嚴格按照資料庫表結構,映射成的物件不需要我們定義,與之不同的是,EF 對這種映射做了一些改進,那就是允許我們定義物體類,也就是允許開發者定義資料庫表被映射成什么型別,這種映射提供了一種更靈活的解決方案,但是它會降低查詢性能,也增加了使用的復雜度,因為需要占用額外的時間去維護資料庫和自定義的物體類間的映射關系,

L2S是由微軟的 C# 團隊完成的,在 Framework 3.5 中發布,而 EF 是由 ADO.NET 團隊在 ADO.NET SP1 中發布的,后來 L2S 的開發和維護由 ADO.NET 團隊來接管,由于開發重心的不同,在 .NET Framework 4.0 中對 L2S 的改變很少,而主要的改進集中在 EF 方面,

盡管在性能上和易用性上,EF 在 .NET Framework 4.0 中已經有了極大的改進,但是兩種技識訓是各有優勢,L2S 的優點是簡單易用、執行性能好,此外它生成的 SQL 陳述句的解釋質量更好一些,EF 的優點是允許我們創建自定義的持久化的物體類,用于資料庫的映射,另外 EF 允許使用同一個查詢機制查詢 SQL Server 之外的資料源,實際上 L2S 也支持這個功能,但是為了鼓勵第三方的查詢機制的出現,L2S 中沒有對外公布這些機制,

EF 4.0 突出的改進是它支持幾乎所有的 L2S 中的查詢方法,

L2S 允許任何類來承載資料,只要類中加入了合適的標簽即可,

[Table] 標簽定義在 System.Data.Linq.Mapping 命名空間中,它定義的型別用來承載資料表中的一行資料,默認情況下,L2S 會認為這個類名和它對應的表名是相同的,如果想讓兩者不同的話,由于表名已經固定,只能更改對應的類名,更改方式是在 [Table] 標簽中顯式地指定類名,

在 L2S 中,如果一個類具有 [Table] 標簽,就稱這個類為物體,為了能夠順利使用,這個物體的結構必須與資料表的結構相匹配,多欄位或少欄位都不行,這種限制使得這種映射是一種低級別的映射,

(P311)

[Column] 標簽用來指示資料表中的某列,如果物體中定義的列名和資料表中的別名不同,那么需要在 [Column] 標簽中特別指出所對應的列名,

[Column] 標簽中的 IsPrimaryKey 屬性用于指示當前列是主鍵,在資料中這列用于唯一標識一條資料,在程式中也用這列區分不同的物體,將物體中的變換更新到資料庫的時候,也需要使用這一列來確定寫入的目標,

總的來講,在定義物體類的時候,L2S 允許將資料庫的欄位映射物件 (物體中的屬性) 定義成私有的,它可以訪問到物體類中的私有變數,

實際上與資料庫表對應的物體類是可以自動生成的,不用逐行書寫,常用的生成工具有 Visual Studio (需要在 “工程” 選單添加一個 “LINQ to SQL Classes” 選項)和命令列工具 SqlMetal ,

和 L2S 中的物體類相似,EF中允許開發者定義自己的物體類用于承載資料,不同的是,EF 中的物體類的定義要靈活得多,在理論上允許任何型別的類來作為物體類使用 (在某些特殊情況下需要實作一些介面) ,也就是說物體類中的結構不用和資料表中的欄位完全對應,

和 L2S 不同的是,在 EF 中,要完成資料的映射和查詢,之定義上面這個物體類是不夠的,因為在 EF 中,查詢并不是直接針對資料庫進行的,它使用了一種更高級別的抽象模型,稱為物體資料模型 (EDM , Entity Data Model) ,我們的查詢陳述句是針對這個模型來定義的,

EDM 實際上是使用 XML 定義的一個 .edmx 型別的檔案,這個檔案包含三部分內容 :

1. 概念模型 : 定義了資料庫的資訊,不同的資料庫有不同的概念模型內容;

2. 存盤模型 : 定義了資料庫的表結構;

3. 映射 : 定義了資料庫表和物體類之間的映射關系;

(P312)

創建 .edmx 檔案最簡單的方式是使用 Visual Studio ,在 “專案” 選單中點擊 “添加新項” ,在彈出的視窗中選擇 “ADO.NET Entity Data Model” ,之后使用向導就可以完成物體類到資料庫表的映射配置,這一系列操作不僅添加一個 .edmx 檔案,還會創建涉及到的物體類,

在 EF 中物體類都是映射到概念模型上,所有對概念模型的查詢和更新操作,都是由 Object Services 發起的,

EF 的設計者在設計的時候將映射關系想得比較簡單,他們假設資料表和物體類之間的映射關系是 1 : 1 的,所以并沒有提供專門的機制去完成一對多或者多對一的映射,盡管這樣,如果確實需要這種特殊的映射關系,還是可以通過修改 .edmx 檔案中的相關內容來實作,下面是幾個常用的修改操作 :

1. 多個表映射到一個物體類;

2. 一個表映射到多個物體類;

3. 按照 ORM 世界中的三種繼承方式將繼承的類映射到表;

三種繼承策略是 :

1. 每個分層結構一張表 : 一張表映射到整個類分層結構,該表中包含分隔符列,用于指出每個行應該映射到哪個類;

2. 每個類一張表 : 一張表映射到一個類,意味著繼承的類映射到多張表,查詢某個物體時,EF 生成 SQL JOIN ,以合并其所有基類;

3. 每個具體類一張表 : 一張單獨的表映射到每個具體的類,這意味著基類映射到多張表,并且在查詢基類的物體時, EF 生成 SQL UNION ;

比較一下,L2S 僅支持每個分層結構一張表,

EF 還支持 LINQ 之外的查詢方式,有一種語言叫 Entity SQL (ESQL),使用這種語言,我們可以通過 EDM 查詢資料庫,這種查詢方式非常便于動態地構建查詢陳述句,

在創建了物體類之后 (如果是 EF 的話還需要有 EDM 檔案),就可以對資料庫進行查詢了,在查詢之前,首先要創建 DataContext (L2S) 或者 ObjectContext (EF) 物件,這個物件用于指定資料庫連接字串,

(P313)

直接創建 DataContext / ObjectContext 實體是一種很底層的使用方式,它可以展示出這兩種型別是如何作業的,但在實際應用中,更常用的方式是創建型別化的 Context (繼承自 DataContext / ObjectContext) 來使用,

對于 L2S 來說,我們只需為 DataContext 傳遞一個資料庫連接字串即可;而對于 EF ,傳遞的是資料庫連接物體,這個物體中除了資料庫連接字串之外,還包括 EDM 檔案的路徑資訊, (如果通過 Visual Studio 創建 EDM 檔案,那么系統會自動在專案的 app.config 檔案中添加完整的資料庫連接物體,可以從這個檔案得到需要的資訊) ,

然后我們就可以使用 GetTable (L2S) 或者 CreateObjectSet (EF) 物件了,這兩個物件都是用于從資料庫中讀取資料,

Single 運算子會根據主鍵從結果集中取出一行記錄,和 First 關鍵字不同的是,Single 運算子要求結果集中只有一條記錄,當結果集中的結果多于一行時,它會拋出例外;而 First 關鍵字在這種情況下則不會拋出例外,

DataContext / ObjectContext 這兩個物件實際上只做兩件事情,第一,它作為一個工廠,將我們查詢的資料組合成物件,第二,它會維護物體類的狀態,如果查詢出物體類中的值在類外改變了,它會記錄下這個欄位,然后便于更新回資料庫,

在 EF 中,唯一的不同點是使用 SaveChanges 方法代替 SubmitChanges 方法,

(P314)

在對資料庫的查詢中,一個更好的方式是為每個資料庫定義一個繼承自 DataContext / ObjectContext 的子類,一般會為每個物體類都添加一個這樣的屬性,這種屬性我們稱之為型別化的 Context ,

盡管 DataContext / ObjectContext 都實作了 IDisposable 介面,而且 Dispose 方法會強制斷開資料庫連接,但是我們一般不通過呼叫 Dispose 方法來銷毀這兩個物件,因為 L2S 和 EF 在回傳查詢結果后會自動斷開連接,

(P315)

DataContext / ObjectContext 物件有跟蹤物體類狀態的功能,當取出一個表中的資料保存到本地記憶體之后,如果下次再到資料庫中查詢某條已經存在的資料, DataContext / ObjectContext 并不會去資料庫中讀取資料,而是直接從記憶體中取出需要的資料,也就是說,在一個 context 的生命周期中,他不會將資料庫中的某行記錄回傳兩次 (資料記錄之間使用主鍵進行區分) ,

L2S 和 EF 都允許關閉物件狀態跟蹤功能,為避免這些限制,在 L2S 中將 DataContext 物件的 ObjectTrackingEnabled 屬性設定成 false 即可,在 EF 中禁用物件跟蹤的功能要麻煩一點,它需要在每個物體中都添加下面的代碼 :

context.Customers.MergeOption = MergeOption.NoTracking;

關閉物件狀態跟蹤功能之后,為了資料安全,通過 context 向資料庫中提交更新的功能也同時被禁用,

(P316)

如果要從資料庫中得到最新的資料,必須定義一個新的 context 物件,將舊的物體類傳給這個物件,然后呼叫 Refresh 方法,這樣,最新的資料就會被更新到物體類中,

在一個多層次的系統中,不能在系統的中間層定義一個靜態的 DataContext 或者 ObjectContext 實體完成所有的資料庫查詢操作,因為 context 物件不能保證執行緒安全,正確的做法是在中間層的方法中,為每個請求的客戶創建一個 context ,這樣做的好處是可以減輕資料庫的負擔,因為維護和更新物體的任務被多個 context 物件分擔,對于資料庫來說,更新操作會通過多個事務執行完成,這顯然比一個很大的事務要高效很多,

使用物體類生成工具還有一個特點,當表之間有關聯關系的時候,我們可以直接使用關聯表中的屬性,物體類自動完成了關聯的欄位和關聯表的映射,

(P317)

L2S 查詢中 [Association] 標簽的作用是提供生成 SQL 陳述句所需的資訊;而 EF 中的 [EdmRelationshipNavigationProperty] 標簽的作用是告訴 EF 要到 EDM 中去查找兩個表的關聯關系,

L2S 和 EF 的查詢方式仍然是延遲加載,在 L2S 查詢中,真正的查詢會在遍歷結果集時進行,而 EF 的查詢則是在顯式地呼叫了 Load 方法之后才會執行,

(P318)

可以通過設定下面這個屬性使 EF 和 L2S 以相同的方式回傳 EntityCollection 和 EntityReferences :

context.ContextOptions.DeferredLoadingEnabled = true;

(P319)

DataLoadOptions 類是 L2S 中一個特有的類,它有兩個作用 :

1. 它允許我們為 EntitySet 所關聯的類指定一個篩選條件;

2. 它可以強制加載特定的 EntitySets ,這樣可以減少整個資料查詢的次數;

(P320)

L2S 和 EF 都會跟蹤物體類的狀態,如果物體中的資料有所改變,我們可以將這些改變更新回資料庫,更新的方式是呼叫 DataContext 類中的 SubmitChanges 方法,在 EF 中則是使用 ObjectContext 物件的 SaveChanges 方法,

除此之外,L2S 的 Table<T> 類還提供了 InsertOnSubmit 和 DeleteOnSubmit 方法用于插入和洗掉資料表中的記錄;而 EF 的 ObjectSet<T> 類提供了 AddObject 和 DeleteObject 方法來完成相同的功能,

(P321)

SubmitChanges / SaveChanges 會記錄 context 創建以來物體類中所有資料變化,然后將這些變化更新回資料庫中,在更新的程序中,需要創建一個 TransactionScope 物件來幫助完成,以免更新程序中造成的錯誤資料,

也可以使用 EntitySet / EntityCollection 類中的 Add 方法向資料庫中添加新的記錄,在呼叫了 SubmitChanges 或者 SaveChanges 方法之后,物體中新添加的記錄的外鍵資訊會被自動取出來,

為新添加的物體物件添加主鍵值比較繁瑣,因為我們需要保證這個主鍵是唯一的,解決辦法是可以在資料庫中定義自增型別的主鍵,或者使用 Guid 作為主鍵,

L2S 能夠識別它們的關聯關系并賦值是因為物體類中有這樣的關聯定義,而 EF 之所以可以自動識別關聯并賦值是因為 EDM 中存盤了這兩種物體間的關聯關系以及關聯欄位,

(P322)

當從 EntitySet / EntityCollection 物件中移除一行后,它的外鍵的值會自動設定成 null ,

L2S 和 EF 的 API 對比 :

1. 各種操作的基礎類 : DataContext (L2S) - ObjectContext (EF);

2. 從資料庫中取出指定型別的所有記錄 : GetTable (L2S) - CreateObjectSet (EF);

3. 方法的回傳型別 : Table<T> (L2S) - ObjectSet<T> (EF);

4. 將物體中的屬性值的變化 (添加、洗掉等) 更新回資料庫 : SubmitChanges (L2S) - SaveChanges (EF);

5. 使用 conetext 更新的方式向資料庫中添加新的記錄 : InsertOnSubmit (L2S) - AddObject (EF);

6. 使用 context 更新的方式洗掉記錄 : DeleteOnSubmit (L2S) - DeleteObject (EF);

7. 關聯表中用于存放多條關聯記錄的屬性 : EntitySet<T> (L2S) - EntityCollection<T> (EF);

8. 關聯表中用于存放單條關聯記錄的屬性 : EntityRef<T> (L2S) - EntityReference<T> (EF);

9. 加載關聯屬性的默認方式 : Lazy (L2S) - Explicit (EF);

10. 構建立即加載的查詢方式 : DataLoadOptions (L2S) - Include() (EF);

(P325)

一個查詢運算式樹是由一個微型的 DOM (Document Object Model ,檔案物件模型) 來描述的,這個 DOM 中每個節點都代表了 System.Linq.Expressions 命名空間中的一個型別,

(P326)

Expression<T> 的基類是 LambdaExpression ,LambdaExpression 是 Lambda 運算式樹中所有節點的基型別,所有的節點型別都可以轉換成這種基型別,因此保證了運算式樹中節點的型別一致性,

Lambda 運算式需要接收引數,而普通的運算式則沒有引數,

【第09章】

(P329)

標準查詢運算子可以分為三類 :

1. 輸入是集合,輸出是集合;

2. 輸入是集合,輸出是單個元素或者標量值;

3. 沒有輸入,輸出是集合 (生成方法) ;

(P330)

[集合] --> [集合]

1. 篩選運算子 —— 回傳原始序列的一個子集,使用的運算子有 : Where 、 Take 、 TakeWhile 、 Skip 、 SkipWhile 、 Distinct ;

2. 映射運算子 —— 這種運算子可以按照 Lambda 運算式指定的形式,將每個輸入元素轉換成輸出元素, SelectMany 用于查詢嵌套的集合;在 LINQ to SQL 和 EF 中 Select 和 SelectMany 運算子可以執行內連接、左外連接、交叉連接以及非等連接等各種連接查詢,使用的運算子有 : Select 、 SelectMany ;

3. 連接運算子 —— 用于將兩個集合連接之后,取得符合條件的元素,連接運算子支持內連接和左外連接,非常適合對本地集合的查詢,使用運算子有 : Join 、 GroupJoin 、 Zip ;

4. 排序運算子 —— 回傳一個經過重新排序的集合,使用的運算子有 : OrderBy 、 ThenBy 、 Reverse ;

(P331)

5. 分組運算子 —— 將一個集合按照某種條件分成幾個不同的子集,使用的運算子有 : GroupBy ;

6. 集合運算子 —— 主要用于對兩個相同型別集合的操作,可以回傳兩個集合中共有的元素、不同的元素或者兩個集合的所有元素,使用的運算子有 : Concat 、 Unoin 、 Intersect 、 Except ;

7. 轉換方法 Import —— 這種方法包括 OfType 、 Cast ;

8. 轉換方法 Export —— 將 IEnumerable<TSource> 型別的集合轉換成一個陣列、清單、字典、檢索或者序列,這種方法包括 : ToArray 、 ToList 、 ToDictionary 、 ToLookup 、 AsEnumerable 、 AsQueryable ;

[集合] --> [單個元素或標量值]

1. 元素運算子 —— 從集合中取出單個特定的元素,使用的運算子有 : First 、 FirstOrDefault 、 Last 、 LastOrDefault 、 Single 、 SingleOrDefault 、 ElementAt 、 ElementAtOrDefault 、 DefaultIfEmpty ;

2. 聚合方法 —— 對集合中的元素進行某種計算,然后回傳一個標量值 (通常是一個數字) ,使用的運算子有 : Aggregate 、 Average 、 Count 、 LongCount 、 Sum 、 Max 、 Min ;

3. 數量詞 —— 一種回傳 true 或者 false 的聚合方法,使用的運算子有 : All 、 Any 、 Contains 、 SequenceEqual ;

(P332)

[空] --> [集合]

第三種查詢運算子不需要輸入但可以輸出一個集合,

生成方法 —— 生成一個簡單的集合,使用的方法有 : Empty 、 Range 、 Repeat ;

(P333)

經過各種方法的篩選,最終得到的序列中的元素只能比原始序列少或者相等,絕不可能比原始序列還多,在篩選程序中,集合中的元素型別及元素值是不會改變的,和輸入時始終保持一致,

如果和 let 陳述句配合使用的話,Where 陳述句可以在一個查詢中出現多次,

(P334)

標準的 C# 變數作用域規則同樣適用于 LINQ 查詢,也就是說,在使用一個查詢變數前,必須先宣告,否則不能使用,

Where 判斷選擇性地接受一個 int 型的第二引數,這個引數用于指定輸入序列中特定位置上的元素,在查詢中可以使用這個數值進行元素的篩選,

下面幾個關鍵字如果用在 string 型別的查詢中將會被轉換成 SQL 中的 LIKE 關鍵字 : Contains 、 StartsWith 、 EndsWith ,

Contains 關鍵字僅用于本地集合的比較,如果想要比較兩個不同列的資料,則需要使用 SqlMethods.Like 方法,

SqlMethods.Like 也可以進行更復雜的比較操作,

在 LINQ to SQL 和 EF 中,可以使用 COntains 方法來查詢一個本地集合,

如果本地集合是一個物件集合或其他非數值型別的集合,LINQ to SQL 或者 EF ,也可能把 Contains 關鍵字翻譯成一個 EXISTS 子查詢,

(P335)

Take 回傳集合的前 n 個元素,并且放棄其余元素;Skip 則是跳過前 n 個元素,并且回傳其余元素,

在 SQL Server 2005 中,LINQ to SQL 和 EF 中的 Take 和 Skip 運算子會被翻譯成 ROW_NUMBER 方法,而在更早的 SQL Server 資料庫版本中則會被翻譯成 Top n 查詢,

TakeWhile 運算子會遍歷輸入集合,然后輸出每個元素,直到給定的判斷為 false 時停止輸出,并忽略剩余的元素,

SkipWhile 運算子會遍歷輸入集合,忽略判斷條件為真之前的每個元素,直到給定的判斷為 false 時輸出剩余的元素,

在 SQL 中沒有與 TakeWhile 和 SkipWhile 對應的查詢方式,如果在 LINQ-to-db 查詢中使用,將會導致一個運行時錯誤,

(P336)

Distinct 的作用是回傳一個沒有重復元素的序列,它會洗掉輸入序列中的重復元素,在這里,判斷兩個元素是否重復的規則是可以自定義的,如果沒有自定義,那么就使用默認的判斷規則,

因為 string 實作了 IEnumerable<char> 介面,所以我們可以在一個字串上直接使用 LINQ 方法,

在查詢一個資料庫時, Select 和 SelectMany 是最常用的連接操作方法;對于本地查詢來說,使用 Join 和 Group 的效率最好,

在使用 Select 時,通常不會減少序列中的元素數量,每個元素可以被轉換成需要的形式,并且這個形式需要通過 Lambda 運算式來定義,

(P337)

在條件查詢中,一般不需要對查詢結果進行映射,之所以要使用 select 運算子,是為了滿足 LINQ 查詢必須以 select 或者 group 陳述句結尾的語法要求,

Select 運算式還接受一個整型的可選引數,這個引數實際上是一個索引,使用它可以得到輸入序列中元素的位置,需要注意的是,這種引數只能在本地查詢中使用,

可以在 Select 陳述句中再嵌套 Select 子句來構成嵌套查詢,這種嵌套查詢的結果是一個多層次的物件集合,

(P338)

內部的子查詢總是針對外部查詢的某個元素進行,

Select 內部的子查詢可以將一個多層次的物件映射成另一個多層次的物件,也可以將一組關聯的單層次物件映射成一個多層次的物件模型,

在對本地集合的查詢中,如果 Select 陳述句中包含 Select 子查詢,那么整個查詢是雙重的延遲加載,

子查詢的映射在 LINQ to SQL 和 EF 中都可以實作,并且可以用來實作 SQL 的連接功能,

(P339)

我們將查詢結果映射到匿名類中,這種映射方式適用于查詢程序中暫存中間結果集的情況,但是當需要將結果回傳給客戶端使用的時候,這種映射方式就不能滿足需求了,因為匿名型別只能在一個方法內作為本地變數存在,

(P341)

SelectMany 可以將兩個集合組成一個更大的集合,

(P342)

在分層次的資料查詢中,使用 SelectMany 和 Select 得到的結果是相同的,但是在查詢單層次的資料源 (如陣列) 的時候,Select 要完成同樣的任務,就需要使用嵌套回圈了,

SelectMany 的好處就是在于,無論輸入集合是什么型別的,它輸出的集合肯定是一個陣列型別的二維集合,結果集的資料不會有層次關系,

在查詢運算式語法中,from 運算子有兩個作用,在查詢一開始的 from 的作用都是引入查詢集合和范圍變數;其他任何位置再出現 from 子句,編譯器都會將其翻譯成 SelectMany ,

(P343)

在需要用到外部變數的情況下,選擇使用查詢運算式語法是最佳選擇,因為在這種情況中,這種語法不僅便于書寫,而且表達方式也更接近查詢邏輯,

(P344)

在 LINQ to SQL 和 EF 中, SelectMany 可以實作交叉連接、不等連接、內連接以及左外連接,

(P345)

在標準 SQL 中,所有的連接都要通過 join 關鍵字實作,

在 Entity Framework 的物體類中,并不會直接存盤一個外鍵值,而是存盤外鍵所關聯物件的集合,所以當需要使用外鍵所關聯的資料時,直接使用物體類屬性中附帶的資料集合即可,不用像 LINQ to SQL 查詢中那樣手動地進行連接來得到外鍵集合中的資料,

對于本地集合的查詢中,為了提高執行效率,應該盡量先篩選,再連接,

如果有需要的話,可以引入新的表來進行連接,查詢時的連接并不限于兩個表之間,多個表也可以進行,在 LINQ 中,可以通過添加一個 from 子句來實作,

(P347)

正確的做法是在 DefaultIfEmpty 運算子之前使用 Where 陳述句,

Join 和 GroupJoin 的作用是連接兩個集合進行查詢,然后回傳一個查詢結果集,他們的不同點在于,Join 回傳的是非嵌套結構的資料集合,而 GroupJoin 回傳的則是嵌套結構的資料集合,

Join 和 GroupJoin 的長處在于對本地集合的查詢,也就是對記憶體中資料的查詢效率比較高,它們的缺點是目前只支持內連接和左外連接,并且連接條件必須是相等連接,需要用到交叉連接或者非等值連接時,就只能選擇 Select 或者 SelectMany 運算子,在 LINQ to SQL 或者 EF 查詢中, Join 和 GroupJoin 運算子在功能上與 Select 和 SelectMany 是沒有什么區別的,

(P352)

當 into 關鍵字出現在 join 后面的時候,編譯器會將 into 關鍵字翻譯成 GroupJoin 來執行,而當 into 出現在 Select 或者 Group 子句之后時,則翻譯成擴展現有的查詢,雖然都是 into 關鍵字,但是出現在不同的地方,差別非常大,有一點它們是相同的,into 關鍵字總是引入一個新的變數,

GroupJoin 的回傳結果實際上是集合的集合,也就是一個集合中的元素還是集合,

(P355)

Zip 是在 .NET Framework 4.0 中新加入的一個運算子,它可以同時列舉兩個集合中的元素 (就像拉鏈的兩邊一樣) ,回傳的集合是經過處理的元素對,

兩個集合中不能配對的元素會直接被忽略,需要注意的是,Zip 運算子只能用于本地集合的查詢,它不支持對資料庫的查詢,

經過排序的集合中的元素值和未排序之前是相同的,只是元素的順序不同,

(P356)

OrderBy 可以按照指定的方式對集合中的元素進行排序,具體的排序方式可以在 KeySelector 運算式中定義,

如果通過 OrderBy 按照指定順序進行排序后,集合中的元素相對順序仍無法確定時,可以使用 ThenBy ,

ThenBy 關鍵字的作用是在前一次排序的基礎上再進行一次排序,在一個查詢中,可以使用任意多個 ThenBy 關鍵字,

(P357)

LINQ 中還提供了 OrderByDescending 和 ThenByDescending 關鍵字,這兩個關鍵字也是用于完成對集合的排序功能,它們的功能和 OrderBy / ThenBy 相同,用法也一樣,只是它們排序后的集合中的元素是按指定欄位的降序排序,

在對本地集合的查詢中,LINQ 會根據默認的 IComparable 介面中的演算法對集合中的元素進行排序,如果不想使用默認的排序方式,可以自己實作一個 IComparable 物件,然后將這個物件傳遞給查詢 LINQ ,

在查詢運算式語法中我們沒有辦法將一個 IComparable 物件傳遞給查詢陳述句,也就不能進行自定義的查詢,

在使用了排序操作的查詢中,排序運算子會將集合轉換成 IEnumerable<T> 型別的一個特殊子類,具體來說,對 Enumerable 型別的集合查詢時,回傳 IOrderedEnumerable 型別的集合;在對 Queryable 型別的集合查詢時,回傳 IOrderedQueryable 型別的集合,這兩種子型別是為排序專門設計的,在它們上面可以直接使用 ThenBy 運算子來進行多次排序,

(P358)

在對遠程資料源的查詢中,需要用 AsQueryable 代替 AsEnumerable ,

(P359)

GroupBy 可以將一個非嵌套的集合按某種條件分組,然后將得到的分組結果以組為單位封裝到一個集合中,

Enumerable.GroupBy 的內部實作是,首先將集合中的所有元素按照鍵值的關系存盤到一個臨時的字典型別的集合中,然后再將這個臨時集合中的所有分組回傳給呼叫者,這里一個分組就是一個鍵和它所對應的一個小集合,

默認情況下,分組之后的元素不會對原始元素做任何處理,如果需要在分組程序中對元素做某些處理的話,可以給元素選擇器指定一個引數,

(P360)

GroupBy 只對集合進行分組,并不做任何排序操作,如果想要對集合進行排序的話,需要使用額外的 OrderBy 關鍵字,

在查詢運算式語法中,GroupBy 可以使用下面這個格式來創建 : group 元素運算式 by 鍵運算式 ,

和其他的查詢一樣,當查詢陳述句中出現了 select 或者 group 的時候,整個查詢就結束了,如果不想讓查詢就此結束,那么就需要擴展整個查詢,可以使用 into 關鍵字,

在 group by 查詢中,經常需要擴展查詢陳述句,因為需要對分組后的集合進一步進行處理,

在 LINQ 中, group by 后面跟著 where 查詢相當于 SQL 中的 HAVING 關鍵字,這個 where 所作用的物件是整個集合或者集合中的每個分組,而不是單個元素,

分組操作同樣適用于對資料庫的查詢,如果是在 EF 中,在使用了關聯屬性的情況下,分組操作并不像在 SQL 中那樣常用,

(P361)

LINQ 中的分組功能對 SQL 中的 “GROUP BY” 進行了很大的擴展,可以認為 LINQ 中的分組是 SQL 中分組功能的一個超集,

和傳統 SQL 查詢不同點是,在 LINQ 中不需要對分組或者排序子句中的變數進行映射,

當需要使用集合中多個鍵來進行分組時,可以使用匿名型別將這幾個鍵封裝到一起,

(P362)

Concat 運算子的作用是合并兩個集合,合并方式是將第一個集合中所有元素放置到結果集中,然后再將第二個集合中的元素放在第一個結果集的后面,然后回傳結果集,Union 執行的也是這種合并操作,但是它最后會將結果集中重復的元素去除,以保證結果集中每個元素都是唯一的,

當對兩個不同型別但基型別卻相同的序列執行合并時,需要顯式地指定這兩個集合的型別以及合并之后的集合型別,

Intersect 運算子用于取出兩個集合中元素的交集,Except 用于取出只出現在第一個集合中的元素,如果某個元素在兩個集合中都存在,那么這個元素就不會包含在結果中,

Enumerable.Except 的內部實作方式是,首先將第一個集合中的所有元素加載到一個字典集合中,然后再對比第二個集合中的元素,如果字典中的某個元素在第二個集合中出現了,那么就將這個元素從字典中移除,

(P363)

從根本上講,LINQ 處理的是 IEnumerable<T> 型別的集合,之所以現在眾多的集合型別都可以使用 LINQ 進行處理,是因為編譯器內部可以將其他型別的序列轉換成 IEnumerable<T> 型別的,

OfType 和 Cast 可以將非 IEnumerable 型別的集合轉換成 IEnumerable<T> 型別的集合,

Cast 和 OfType 運算子的唯一不同就是它們遇到不相容型別時的處理方式 : Cast 會拋出例外,而 OfType 則會忽略這個型別不相容的元素,

元素相容的規則與 C# 的 is 運算子完全相同,因此只能考慮參考轉換和拆箱轉換,

Cast 運算子的內部實作與 OfType 完全相同,只是省略了型別檢查那行代碼,

OfType 和 Cast 的另一個重要功能是 : 按型別從集合中取出元素,

(P365)

ToArray 和 ToList 可以分別將集合轉換成陣列和泛型集合,這兩個運算子也會強制 LINQ 查詢陳述句立即執行,也就是說當整個查詢是延遲加載的時候,一旦遇到 ToArray 或者 ToList ,整個陳述句會被立即執行,

ToDictionary 方法也會強制查詢陳述句立即執行,然后將查詢結果放在一個 Dictionary 型別的集合中, ToDictionary 方法中的鍵選擇器必須為每個元素提供一個唯一的鍵,也就是說不同元素的鍵是不能重復的,否則在查詢的時候系統會拋出例外,而 Tolookup 方法的要求則不同,它允許多個元素共用相同的鍵,

AsEnumerable 將一個其他型別的集合轉換成 IEnumerable<T> 型別,這樣可以強制編譯器使用 Enumerable 類中的方法來決議查詢中的運算子,

AsQueryable 方法則會將一個其他型別的集合轉換成 IQueryable<T> 型別的集合,前提是被轉換的集合實作了 IQueryable<T> 介面,否則 IQueryable<T> 會實體化一個物件,然后存盤在本地陣列外面,看起來是可以呼叫 IQueryable 中的方法,但實際上這些方法并沒有真正的意義,

(P366)

所有以 "OrDefault" 結尾的方法有一個共同點,那就是當集合為慷訓者集合中沒有符合要求的元素時,這些方法不拋出例外,而是回傳一個默認型別的值 default(TSource) ,

對于參考型別的元素來說 default(TSource) 是 null ,而對于值型別的元素來說,這個默認值通常是 0 ,

為了避免出現例外,在使用 Single 運算子時必須保證集合中有且僅有一個元素;而 SingleOrDefault 運算子則要求集合中有一個或零個元素,

Single 是所有元素運算子中要求最多的,而 FirstOrDefault 和 LastOrDefault 則對集合中的元素沒有什么要求,

(P367)

在 LINQ to SQL 和 EF 中, Single 運算子通常應用于使用主鍵到資料庫中查找特定的單個元素,

ElementAt 運算子可以根據指定的下標取出集合中的元素,

Enumerable.ElementAt 的實作方式是,如果它所查詢的集合實作了 IList<T> 介面,那么在取元素的時候,就使用 IList<T> 中的索引器,否則,就使用自定義的回圈方法,在回圈中依次向后查找元素,回圈 n 次之后,回傳下一個元素,ElementAt 運算子不能在 LINQ to SQL 和 EF 中使用,

DefaultIfEmpty 可以將一個空的集合轉換成 null 或者 default() 型別,這個運算子一般用于定義外連接查詢,

(P368)

Count 運算子的作用是回傳集合中元素的個數,

Enumerable.Count 方法的內部實作方式如下 : 首先判斷輸入集合有沒有實作 ICollection<T> 介面,如果實作了,那么它的就呼叫 ICollection<T>.Count 方法得到元素個數,否則就遍歷整個集合中的元素,統計出元素的個數,然后回傳,

還可以為 Count 這個方法添加一個篩選條件,

LongCount 運算子的作用和 Count 是相同的,只是它的回傳值型別是 int64 ,也就是它能用于大資料量的統計, int64 能統計大概 20 億個元素的集合,

Min 和 Max 回傳集合中最小和最大的元素,

如果集合沒有實作 IComparable<T> 介面的話,那么我們就必須為這兩個運算子提供選擇器,

選擇器運算式不僅定義了元素的比較方式,還定義了最后的結果集的型別,

(P369)

Sum 和 Average 的回傳值型別是有限的,它們內置了以下幾種固定的回傳值型別 : int 、 long 、 float 、 double 、 decimal 以及這幾種型別的可空型別,這里回傳值都是值型別,也就是,Sum 和 Average 的預期結果都是數字,而 Min 和 Max 則會回傳所有實作了 IComparable<T> 介面的型別,

更進一步講, Average 值回傳兩種型別 : decimal 和 double ,

Average 為了避免查詢程序中數值的精度損失,會自動將回傳值型別的精度升高一級,

(P370)

Aggregate 運算子我們可以自定義聚合方法,這個運算子只能用于本地集合的查詢中,不支持 LINQ to SQL 和 EF ,這個運算子的具體功能要根據它在特定情況下的定義來看,

Aggregate 運算子的第一個引數是一個種子,用于指示統計結果的初始值是多少;第二個引數是一個運算式,用于更新統計結果,并將統計結果賦值給新的變數;第三個引數是可選的,用于將統計結果映射成期望的形式,

Aggregate 運算子最大的問題是,它實作的功能通過 foreach 陳述句也可以實作,而且 foreach 陳述句的語法更清晰明了, Aggregate 的主要用處在于處理比較大或者比較復雜的聚合操作,

(P372)

Contains 關鍵字接收一個 TSource 型別的引數;而 Any 的引數則定義了篩選條件,這個引數是可選的,

Any 關鍵字對集合中元素的要求低一點,只要集合中有一個元素符合要求,就回傳 true ,

Any 包含了 Contains 關鍵字的所有功能,

如果在使用 Any 關鍵字的時候不帶引數,那么只要集合中有一個元素符合要求,就回傳 true ,

Any 關鍵字在子查詢中使用特別廣泛,尤其是在對資料庫的查詢中,

當集合中的元素都符合給定的條件時, All 運算子回傳 true ,

SequenceEqual 用于比較兩個集合中的元素是否相同,如果相同則回傳 true ,它的篩選條件要求元素個數相同、元素內容相同而且元素在集合中的順序也必須是相同的,

(P373)

Empty 、 Repeat 和 Range 都是靜態的非擴展方法,它們只能用于本地集合中,

Empty 用于創建一個空的集合,它需要接收一個用于標識集合型別的引數,

和 “??” 運算子配合使用的話,Empty 運算子可以實作 DefaultEmpty 的功能,

Range 和 Repeat 運算子只能使用在整型集合中,

Range 接收兩個引數,分別用于指示起始元素的下標和查詢元素的個數,

Repeat 接收兩個引數,第一個引數是要創建的元素,第二個引數用于指示重復元素的個數,

【第10章】

(P375)

在 .NET Framework 中提供了很多用于處理 XML 資料的 API ,從 .NET Framework 3.5 之后,LINQ to XML 成為處理通用 XML 檔案的首選工具,它提供了一個輕量的集成了 LINQ 友好的 XML 檔案物件模型,當然還有相應的查詢運算子,在大多數情況下,它完全可以替代之前 W3C 標準的 DOM 模型 (又稱為 XmlDocument) ,

LINQ to XML 中 DOM 的設計非常完善且高效,即使沒有 LINQ ,單純的 LINQ to XML 中 DOM 對底層 XmlReader 和 XmlWriter 類也進行了很好的封裝,可以通過它來更簡單地使用這兩個類中的方法,

LINQ to XML 中所有的型別定義都包含在 System.Xml.Linq 命名空間中,

所有 XML 檔案一樣,在檔案開始都是宣告部分,然后是根元素,

屬性由兩部分組成 : 屬性名和屬性值,

(P376)

宣告、元素、屬性、值和文本內容這些結構都可以用類來表示,如果這種類有很多屬性來存盤子內容,我們可以用一個物件樹來完全描述檔案,這個樹狀結構就是檔案物件模型 (Document Object Model) ,簡稱 DOM ,

LINQ to XML 由兩部分組成 :

1. 一個 XML DOM ,我們稱之為 X-DOM ;

2. 約 10 個用于查詢的運算子;

可以想象, X-DOM 是由諸如 XDocument 、 XElement 、 XAttribute 等類組成的,有意思的是, X-DOM 類并沒有和 LINQ 系結在一起,也就是說,即使不使用 LINQ 查詢,也可以加載、更新或存盤 X-DOM ,

X-DOM 是集成了 LINQ 的模型 :

1. X-DOM 中的一些方法可以回傳 IEnumerable 型別的集合,使 LINQ 查詢變得非常方便;

2. X-DOM 的構造方法更加靈活,可以通過 LINQ 將資料直接映射成 X-DOM 樹;

XObject 是整個繼承結構的根, XElement 和 XDocument 則是平行結構的根,

XObject 是所有 X-DOM 內容的抽象基類,在這個型別中定義了一個指向 Parent 元素的鏈接,這樣就可以確定節點之間的層次關系,另外這個類中還有一個 XDocument 型別的物件可供使用,

除了屬性之外, XNode 是其他大部分 X-DOM 內容的基類, XNode 的一個重要特性是它可以被有順序地存放在一個混合型別的 XNodes 集合中,

XAttribute 物件的存盤方式 —— 多個 XAttribute 物件必須成對存放,

(P377)

雖然 XNode 可以訪問它的父節點 XElement ,但是它卻對自己的子節點一無所知,因為管理子節點的作業是由子類 XContainer 來做的, XContainer 中定義了一系列成員和方法來管理它的子類,并且是 XElement 和 XDocument 的抽象基類,

除了 Name 和 Value 之外, XElement 還定義了其他的成員來管理自己的屬性,在絕大多數情況下, XElement 會包含一個 XText 型別的子節點, XElement 的 Value 屬性同時包含了存取這個 XText 節點的 get 和 set 操作,這樣可以更方便地設定節點值,由于 Value 屬性的存在,我們可以不必直接使用 XText 物件,這使得對節點的賦值操作變得非常簡單,

(P378)

XML 樹的根節點是 XDocument 物件,更準確地說,它封裝了根 XElement ,添加了 XDeclaration 以及一些根節點需要執行的指令,與 W3C 標準的 DOM 有所不同,即使沒有創建 XDocument 也可以加載、操作和保存 X-DOM ,這種對 XDocument 的不依賴性使得我們可以很容易將一個節點子樹移到另一個 X-DOM 層次結構中,

XElement 和 XDocument 都提供了靜態 Load 和 Parse 方法,使用這兩個方法,開發者可以根據已有的資料創建 X-DOM :

1. Load 可以根據檔案、 URI 、 Stream 、 TextReader 或者 XmlReader 等構建 X-DOM ;

2. Parse 可以根據字串構建 X-DOM ;

(P379)

在節點上呼叫 ToString 方法可將這個節點中的內容轉換成 XML 字串,默認情況下,轉換后的 XML 字串是經過格式化的,即使用換行和空格將 XML 字串按層次結構逐行輸出,且使用正確的縮進格式,如果不想讓 ToString 方法格式化 XML ,那么可以指定 SaveOptions.DisableFormatting 引數,

XElement 和 XDocument 還分別提供了 Save 方法,使用這個方法可將 X-DOM 寫入檔案、 Stream 、 TextWriter 或者 XmlWriter 中,如果選擇將 X-DOM 寫入到一個檔案中,則會自動寫入 XML 宣告部分,另外, XNode 類還提供了一個 WriteTo 方法,這個方法只能向 XmlWriter 中寫入資料,

創建 X-DOM 樹常用的方法是手動實體化多個節點,然后通過 XContainer 的 Add 方法將所有節點拼裝成 XML 樹,而不是通過 Load 或者 Parse 方法,

要構建 XElement 和 XAttribute ,只需提供屬性名和屬性值,

構建 XElement 時,屬性值不是必須的,可以只提供一個元素名并在其后添加內容,

注意,當需要為一個物件添加屬性值時,只需設定一個字串即可,不用顯式創建并添加 XText 子節點, X-DOM 的內部機制會自動完成這個操作,這使得活加屬性值變得更加容易,

(P380)

X-DOM 還支持另一種實體化方式 : 函式型構建 (源于函式式編程) ,

這種構建方式有兩個優點 : 第一,代碼可以體現出 XML 的結構;第二,這種運算式可以包含在 LINQ 查詢的 select 子句中,

之所以以函式型構建的方式定義 XML 檔案,是因為 XElement (和 XDocument) 的構造方法都可多載,以接受 params 物件陣列 : public XElement(XName name, params object[] content) ,

XContainer 類的 Add 方法同樣也接收這種型別的引數 : public void Add(params object[] content) ,

所以,我們可以在構建或添加 X-DOM 時指定任意數目、任意型別的子物件,這是因為任何內容都是合法的,

XContainer 類內部的決議方式 :

1. 如果傳入的物件是 null ,那么就忽略這個節點;

2. 如果傳入物件是以 XNode 或者 XStreamingElement 作為基類,那么就將這個物件添加為 Node 物件,放到 Nodes 集合中;

3. 如果傳入物件是 XAttribute ,那么就將這個物件作為 Attribute 集合來處理;

4. 如果物件是 string ,那么這個物件會被封裝成一個 XText 節點,然后添加到 Nodes 集合中;

5. 如果物件實作了 IEnumerable 介面,則對其進行列舉,每個元素都按照上面的規則來處理;

6. 如果某個型別不符合上述任一條件,那么這個物件會被轉換成 string ,然后被封裝在 XText 節點上,并添加到 Nodes 集合中;

上述所有情況最終都是 : Nodes 或 Attributes ,另外,所有物件都是有效的,因為最終肯定可以呼叫它的 ToString 方法并將其作為 XText 節點來處理,

實際上, X-DOM 內部在處理 string 型別的物件時,會自動執行一些優化操作,也就是簡單地將文本內容存放在字串中,直到 XContainer 上呼叫 Nodes 方法時,才會生成實際的 XText 節點,

(P382)

與在 XML 中一樣, X-DOM 中的元素和屬性名是區分大小寫的,

使用 FirstNode 與 LastNode 可以直接訪問第一個或最后一個子節點;Nodes 回傳所有的子節點并形成一個序列,這三個函式只用于直系的子節點,

(P383)

Elements() 方法回傳型別為 XElement 的子節點,

Elements() 方法還可以只回傳指定名字的元素,

(P384)

Element() 方法回傳匹配給定名稱的第一個元素,Element 對于簡單的導航是非常有用的,

Element 的作用相當于呼叫 Elements() ,然后再應用 LINQ 的 FirstOrDefault 查詢運算子給定一個名稱作為匹配斷言,如果沒有找到所請求的元素,則 Element 回傳 null ,

XContainer 還定義了 Descendants 和 DescendantNodes 方法,它們遞回地回傳子元素或子節點,

Descendants 接受一個可選的元素名,

(P385)

所有的 XNodes 都包含一個 Parent 屬性,另外還有一個 AncestorXXX 方法用來找到特定的父節點,一個父節點永遠是一個 XElement ,

Ancestors 回傳一個序列,其第一個元素是 Parent ,下一個元素則是 Parent.Parent ,依次類推,直到根元素,

還可以使用 LINQ 查詢 AncestorsAndSelf().Last() 來取得根元素,

另外一種方法是呼叫 Document.Root ,但只有存在 XDocument 時才能執行,

使用 PreviousNode 和 NextNode (以及 FirstNode / LastNode) 方法查找節點時,相當于從一個鏈表中遍歷所有節點,事實上 XML 中節點的存盤結構確實是鏈表,

(P386)

XNode 存盤在一個單向鏈表中,所以 PreviousNode 并不是當前元素的前序元素,

Attributes 方法接受一個名稱并回傳包含 0 或 1 個元素的序列;在 XML 中,元素不能包含重復的屬性名,

可以使用下面這幾種方式來更新 XML 中的元素和屬性 :

1. 呼叫 SetValue 方法或者重新給 Value 屬性賦值;

2. 呼叫 SetElementValue 或 SetAttributeValue 方法;

3. 呼叫某個 RemoveXXX 方法;

4. 呼叫某個 AddXXX 或 ReplaceXXX 方法指定更新的內容;

也可以為 XElement 物件重新設定 Name 屬性,

使用 SetValue 方法可以使用簡單的值替換元素或者屬性中原來的值,通過 Value 屬性賦值會達到相同的效果,但只能使用 string 型別的資料,

呼叫 SetValue 方法 (或者為 Value 重新賦值) 的結果就是它替換了所有的子節點,

(P387)

最好的兩個方法是 : SetElementValue 和 SetAttributeValue ,它們提供了一種非常便捷的方式來實體化 XElement 或 XAttribute 物件,然后呼叫父節點的 Add 方法,將新節點加入到父節點下面,從而替換相同名稱的任何現有元素或屬性,

Add 方法將一個子節點添加到一個元素或檔案中,AddFirst 也一樣,但它將節點插入集合的開頭而不是結尾,

我們也可以通過呼叫 RemoveNodes 或 RemoveAttributes 將所有的子節點或屬性全部洗掉, RemoveAll 相當于同時呼叫了這兩個方法,

ReplaceXXX 方法等價于呼叫 Removing ,然后再呼叫 Adding ,它們擁有輸入引數的快照,因此 e.ReplaceNodes(e.Nodes) 可以正常進行,

AddBeforeSelf 、 AddAfterSelf 、 Remove 和 ReplaceWith 方法不能操作一個節點的子節點,它們只能操作當前節點所在的集合,這就要求當前節點都有父元素,否則在使用這些方法時就會拋出例外,此時 AddBeforeSelf 和 AddAfterSelf 方法非常有用,這兩個方法可以將一個新節點插入到 XML 中的任意位置,

(P388)

Remove 方法可以將當前節點從它的父節點中移除,ReplaceWith 方法實作同樣的操作,只是它在移除舊節點之后還會在同一位置插入其他內容,

通過 System.Xml.Linq 中的擴展方法,我們可以使用 Remove 方法整組地移除節點或者屬性,

(P389)

Remove 方法的內部實作機制是這樣的 : 首先將所有匹配的元素讀取到一個臨時串列中,然后列舉該臨時串列并執行洗掉操作,這避免了在洗掉的同時進行查詢操作所引起的錯誤,

XElement 和 XAttribute 都有一個 string 型別的 Value 屬性,如果一個元素有 XText 型別的子節點,那么 XElement 的 Value 屬性就相當于訪問此節點的快捷方式,對于 XAttribute 的 Value 屬性就是指屬性值,

有兩種方式可以設定 Value 屬性值 : 呼叫 SetValue 方法或者直接給 Value 屬性賦值, SetValue 方法要復雜一些,因為它不僅可以接收 string 型別的引數,也可以設定其他簡單的資料型別,

(P390)

由于有了 Value 的值,你可能會好奇什么時候才需要直接和 XText 節點打交道?答案是 : 當擁有混合內容時,

(P391)

向 XElement 添加簡單的內容時, X-DOM 會將新添加的內容附加到現有的 XText 節點后面,而不會新建一個 XText 節點,

如果顯式地指定創建新的 XText 節點,最侄訓得到多個子節點,

XDocument 封裝了根節點 XElement ,可以添加 XDeclaration 、處理指令、說明檔案型別以及根級別的注釋,

XDocument 是可選的,并且能夠被忽略或者省略,這點與 W3C DOM 不同,

XDocument 提供了和 XElement 相同的構造方法,另外由于它也繼承了 XContainer 類,所以也支持 AddXXX 、 RemoveXXX 和 ReplaceXXX 等方法,但與 XElement 不同,一個 XDocument 節點可添加的內容是有限的 :

1. 一個 XElement 物件 (根節點) ;

2. 一個 XDeclaration 物件;

3. 一個 XDocumentType 物件 (參考一個 DTD) ;

4. 任意數目的 XProcessingInstruction 物件;

5. 任意數目的 XComment 物件;

(P392)

對于 XDocument 來說,只有根 XElement 物件是必須的, XDeclaration 是可選的,如果省略,在序列化的程序中會應用默認設定,

(P393)

XDocument 有一個 Root 屬性,這個屬性是取得當前 XDocument 物件單個 XElement 的快捷方式,其反向的鏈接是由 XObject 的 Document 屬性提供的,并且可以應用于樹中的所有物件,

XDocument 物件的子節點是沒有 Parent 資訊的,

XDeclaration 并不是 XNode 型別的,因此它不會出現在檔案的 Nodes 集合中,而注釋、處理指令和根元素等都會出現在 Nodes 集合中,

XDeclaration 物件專門存放在一個 Declaration 屬性中,

XML 宣告是為了保證整個檔案被 XML 閱讀器正確決議并理解,

XElement 和 XDocument 都遵循下面這些 XML 宣告的規則 :

1. 在一個檔案名上呼叫 Save 方法時,總是自動寫入 XML 宣告;

2. 在 XmlWriter 物件上呼叫 Save 方法時,除非 XmlWriter 特別指出,都則都會寫入 XML 宣告;

3. ToString 方法從來都不回傳 XML 宣告;

如果不想讓 XmlWriter 創建 XML 宣告,可以在構建 XmlWriter 物件時,通過設定 XmlWriterSettings 物件的 OmitXmlDeclaration 和 ConformanceLevel 屬性來實作,

是否有 XDeclaration 物件對是否寫入 XML 宣告沒有任何影響, XDeclaration 的目的是提示進行 XML 序列化行程,方式有兩種 :

1. 使用的文本編碼標準;

2. 定義 XML 宣告中 encoding 和 standalone 兩個屬性的值 (如果寫入宣告) ;

XDeclaration 的構造方法接受三個引數,分別用于設定 version 、 encoding 和 standalone 屬性,

(P394)

XML 撰寫器會忽略所指定的 XML 版本資訊,始終寫入 “1.0” ,

需要注意的是,XML 宣告中指定的必須是諸如 “utf-16” 這樣的 IETF 編碼方式,

XML 命名空間有兩個功能,首先,與 C# 的命名空間一樣,它們可以幫助避免命名沖突,當要合并來自兩個不同 XML 檔案的資料時,這可能會成為一個問題,其次,命名空間賦予了名稱一個絕對的意義,

(P395)

xmlns 是一個特殊的保留屬性,以上用法使它執行下面兩種功能 :

1. 它為有疑問的元素指定了一個命名空間;

2. 它為所有后代元素指定了一個默認的命名空間;

有前綴的元素不會為它的后代元素定義默認的命名空間,

(P396)

使用 URI (自定義的 URI) 作為命名空間是一種通用的做法,這可以有效地保證命名空間的唯一性,

對于屬性來說,最好不使用命名空間,因為屬性往往是對本地元素起作用,

有多種方式可以指定 XML 命名空間,第一種方式是在本地名字前面使用大括號來指定,第二種方式 (也是更好的一種方式) 是通過 XNamespace 和 XName 為 XML 設定命名空間,

(P397)

XName 還多載了 + 運算子,這樣無需使用大括號即可直接將命名空間和元素組合在一起,

在 X-DOM 中有很多構造方法和方法都能接受元素名或者屬性名作為引數,但它們實際上接受 XName 物件,而不是字串,到目前為止我們都是在用字串作引數,之所以可以這么用,是因為字串可以被隱式轉換成 XName 物件,

除非需要輸出 XML ,否則 X-DOM 會忽略默認命名空間的概念,這意味著,如果要構建子 XElement ,必須顯式地指定命名空間,因為子元素不會從父元素繼承命名空間,

(P398)

在使用命名空間時,一個很容易犯的錯誤是在查找 XML 的元素時沒有指定它所屬的命名空間,

如果在構建 X-DOM 樹時沒有指定命名空間,可以在隨后的代碼中為每個元素分配一個命名空間,

【第11章】

(P407)

System.Xml ,命名空間由以下命名空間和核心類組成 :

System.Xml.* ——

1. XmlReader 和 XmlWriter : 高性能、只向前地讀寫 XML 流;

2. XmlDocument : 代表基于 W3C 標準的檔案物件模型 (DOM) 的 XML 檔案;

System.Xml.XPath —— 為 XPath (一種基于字串的查詢 XML 的語言) 提供基礎結構和 API (XPathNavigator 類) ;

System.Xml.XmlSchema —— 為 (W3C) XSD 提供基礎機構和 API ;

System.Xml.Xsl —— 為使用 (W3C) XSLT 對 XML 進行決議提供基礎結構和 API ;

System.Xml.Serialization —— 提供類和 XML 之間的序列化;

System.Xml.XLinq —— 先進的、簡化的、 LINQ 版本的 XmlDocument ,

W3C 是 World Web Consortium (萬維網聯盟) 的縮寫,定義了 XML 標準,

靜態類 XmlConvert 是決議和格式化 XML 字串的類,

XmlReader 是一個高性能的類,能夠以低級別、只向前的方式讀取 XML 流,

(P408)

通過呼叫靜態方法 XmlReader.Create 來實體化一個 XmlReader 物件,可以向這個方法傳遞一個 Stream 、 TextReader 或者 URI 字串,

因為 XmlReader 可以讀取一些可能速度較慢的資料源 (Stream 和 URI) ,所以它為大多數方法提供了異步版本,這樣我們可以方便撰寫非阻塞代碼,

XML 流以 XML 節點為單位,讀取器按文本順序 (深度優先) 來遍歷 XML 流, Depth 屬性回傳游標的當前深度,

從 XmlReader 讀取節點的最基本的方法是呼叫 Read 方法,它指向 XML 流的下一個節點,相當于 IEnumerator 的 MoveNext 方法,第一次呼叫 Read 會把游標放置在第一個節點,當 Read 方法回傳 false 時,說明游標已經到達最后一個節點 在這個時候 XmlReader 應該被關閉,

(P409)

屬性沒有包含在基于 Read 的遍歷中,

XmlReader 提供了 Name 和 Value 這兩個 string 型別的屬性來訪問節點的內容,根據節點型別,內容可能定義在 Name 或 Value 上,或者兩者都有,

(P410)

驗證失敗會導致 XmlReader 拋出 XmlException ,這個例外包含錯誤發生的行號 (LineNumber) 和位置 (LinePosition) ,當 XML 檔案很大時記錄這些資訊會比較關鍵,

(P413)

XmlReader 提供了一個索引器以直接 (隨機) 地通過名字或位置來訪問一個節點的屬性,使用索引器等同于呼叫 GetAttributes 方法,

(P415)

XmlWriter 是一個 XML 流的只向前的撰寫器, XmlWriter 的設計和 XmlReader 是對稱的,

和 XmlReader 一樣,可以通過呼叫靜態方法 Create 來構建一個 XmlWriter ,

(P416)

除非使用 XmlWriterSettings ,并設定其 OmitXmlDeclaration 為 true 或者 ConfermanceLevel 為 Fragment ,否則 XmlWriter 會自動地在頂部寫上宣告,并且后者允許寫多個根節點,如果不設定的話會拋出例外,

WriteValue 方法寫一個文本節點,它不僅接受 string 型別的引數,還可以接受像 bool 、 DateTime 型別的引數,實際在內部呼叫了 XmlConvert 來實作符合 XML 字串決議,

WriteString 和呼叫 WriteValue 傳遞一個 string 引數實作的操作是等價的,

在寫完開始節點后可以立即寫屬性,

(P417)

WriteRaw 直接向輸出流注入一個字串,也可以通過接受 XmlReader 的 WriteNode 方法,把 XmlReader 中的所有內容寫入輸出流,

XmlWriter 使代碼非常簡潔,如果相同的命名空間在父元素上已宣告,它會自動地省略子元素上命名空間的宣告,

(P420)

可以在使用 XmlReader 或 XmlWriter 使代碼復雜時使用 X-DOM ,使用 X-DOM 是處理內部元素的最佳方式,這樣就可以兼并 X-DOM 的易用性和 XmlReader 、 XmlWriter 低記憶體消耗的特點,

(P421)

XmlDocument 是一個 XML 檔案的記憶體表示,這個型別的物件模型和方法與 W3C 所定義的模式一致,如果你熟悉其他符合 W3C 的 XML DOM 技術,就會同樣熟悉 XmlDocument 類,但是如果和 X-DOM 相比的話, W3C 模型就顯得過于復雜,

(P422)

可以實體化一個 XmlDocument ,然后呼叫 Load 或 LoadXml 來從一個已知的源加載一個 XmlDocument :

1. Load 接受一個檔案名、 流 (Stream) 、 文本讀取器 (TextReader) 或者 XML 讀取器 (XmlReader) ;

2. LoadXml 接受一個 XML 字串;

相對應的,通過呼叫 Save 方法,傳遞檔案名, Stream 、 TextReader 或者 XmlWriter 引數來保存一個檔案,

通過定義在 XNode 上的 ChildNodes 屬性可以深入到此節點的下層樹型結構,它回傳一個可索引的集合,

而使用 ParentNode 屬性,可以回傳其父節點,

XmlNode 定義了一個 Attributes 屬性用來通過名字或命名空間或順序位置來訪問屬性,

(P423)

InnerText 屬性代表所有子文本節點的聯合,

設定 InnerText 屬性會用一個文本節點替換所有子節點,所以在設定這個屬性時要謹慎以防止不小心覆寫了所有子節點,

InnerXml 屬性表示當前節點中的 XML 片段,

如果節點型別不能有子節點, InnerXml 會拋出一個例外,

XmlDocument 創建和添加新節點 :

1. 呼叫 XmlDocument 其中一個 CreateXXX 方法;

2. 在父節點上呼叫 AppendChild 、 PrependChild 、 InsertBefore 或者 InsertAfter 來添加新節點到樹上;

要創建節點,首先要有一個 XmlDocument ,不能像 X-DOM 那樣簡單地實體化一個 XmlElement ,節點需要 “寄生” 在一個 XmlDocument 宿主上,

(P424)

可以以任何屬順序來構建這棵樹,即便重新排列添加子節點后的陳述句順序,對此也沒有影響,

也可以呼叫 RemoveChild 、 ReplaceChild 或者 RemoveAll 來移除節點,

使用 CreateElement 和 CreateAttribute 的多載方法可以指定命名空間和前綴,

CreateXXX (string name);
CreateXXX (string name, string namespaceURI);
CreateXXX (string prefix, string localName, string namespaceURI);

引數 name 既可以是本地名稱 (沒有前綴) ,也可以是帶前綴的名稱,

引數 namespaceURI 用在當且僅當宣告 (而不是僅在參考) 一個命名空間時,

XPath 是 XML 查詢的 W3C 標準,在 .NET Framework 中, XPath 可以查詢一個 XmlDocument ,就像用 LINQ 查詢 X-DOM ,然而 XPath 應用更廣泛,它也在其他 XML 技術中被使用,例如 XML Schema 、 XLST 和 XAML ,

XPath 查詢按照 XPath 2.0 資料模型 (XPath Data Model) 來表示, DOM 和 XPath 資料模型都表示一個 XML 檔案樹,區別是 XPath 資料模型純粹以資料為中心,采取了 XML 文本的格式,例如在 XPath 資料模型中,CDATA 部分不是必需的,因為 CDATA 存在的唯一原因是可以在文本中包含 XML 的一些識別符號,

(P425)

可以使用下面的方式在代碼中實作 XPath 查詢 :

1. 在一個 XmlDocument 或 XmlNode 上呼叫 SelectXXX 方法;

2. 從一個 XmlDocument 或者 XPathDocument 上生成一個 XPathNavigator ;

3. 在 XNode 上呼叫一個 XPathXXX 擴展方法;

SelectXXX 方法接受一個 XPath 查詢字串,

(P426)

XPathNavigator 是 XML 檔案的 XPath 資料模型上的一個游標,他被加載并提供了一些基本方法可以在檔案樹上移動游標,

XPathNavigator 的 Select* 方法可以使用一個 XPath 字串來表達更復雜的導航或查詢以回傳多個節點,

可以從一個 XmlDocument 、 XPathDocument 或者另一個 XPathNavigator 上來生成 XPathNavigator 實體,

(P427)

在 XPath 資料模型中,一個節點的值是文本元素的連接,等同于 XmlDocument 的 InnerText 屬性,

SelectSingleNode 方法回傳一個 XPathNavigator , Select 方法回傳一個 XPathNodeInterator 以在多個 XPathNavigator 上進行簡便地遍歷,

為了更快地查詢,可以把 XPath 編譯成一個 XPathExpression ,然后傳遞給 Select* 方法,

(P428)

XmlDocument 和 XPathNavigator 的 Select* 方法有對應的多載函式來接受一個 XmlNamespaceManager ,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/60033.html

標籤:其他

上一篇:古風云渺配樂試聽

下一篇:C#中Equals和GetHashCode

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more