《修煉之道:.NET 開發要點精講》
作者:周見智;博圖軒
第 1 章 另辟蹊徑:解讀.NET
1.7 本章思考 > 位置 465
1. 簡述. NET 平臺 中 CTS、 CLS 以及 CLR 的 含義 與 作用,
A: CTS 指 公共 型別 系統, 是. NET 平臺 中 各種 語言 必須 遵守 的 型別 規范; CLS 指 公共 語言 規范, 是. NET 平臺 中 各種 語言 必須 遵守 的 語言 規范; CLR 指 公共 語言 運行時, 它是 一個 虛擬 機,. NET 平臺 中 所有 的 托管 代碼 均需 要在 CLR 中 運行, 可將 其 視為 另外 一個 作業系統,
第 2 章 高屋建瓴:梳理編程約定
2.2 方法與執行緒的關系 > 位置 519
只要 我們 確定 了 兩個 方法 只會 運行 在 同一個 執行緒 中, 那么 這 兩個 方法 就不 可能 同時 執行, 跟 方法 所處 的 位置 無關,
只可 能 一前一后 執行, 此時 我們 不需要 考慮 方法 中 訪問 的 公共 資源 的 執行緒 是否 安全,
2.7 執行緒安全 > 位置 595
如果 操作 一個 物件( 比如 呼叫 它的 方法 或者 給 屬性 賦值) 為 非 原子 操作, 即可 能 操作 還沒 完成 就 暫停 了, 這個時候 如果 有 另外 一個 執行緒 開始 運行 同時 也 操作 這個 物件, 訪問 了 同樣 的 方法( 或 屬性), 那么 這時 就可能 會 出現 一個 問題: 前一 個 操作 還未 結束, 后 一個 操作 就 開始 了, 前后 兩個 操作 一起 出現 混亂, 當 多個 執行緒 同時 訪問 一個 物件 時, 如果 每次 執行 都得 到 不一樣 的 結果, 甚至 出現 例外, 那么 這個 物件 便是 “非 執行緒 安全”, 造成 一個 物件 非 執行緒 安全 的 因素 有 很多, 除了 前面 提到 的 由于 非 原子 操作 執行 到 一半 就 中斷 以外, 還有 一種 情況 是由 多個 CPU 造成 的, 即 就算 操作 沒有 中斷, 由于 多個 CPU 可以 真正 實作 多 執行緒 同時 運行, 所以 就有 可能 出現 “ 對 同一 物件 同時 操作 出現 混亂” 的 情況,如圖 2- 4 所示,
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖 [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖](https://img.uj5u.com/2020/10/25/161444251815171.png)
2.7 執行緒安全 > 位置 627
在 Winform 編程 中, 我們 之所以 經常 會 遇見 “不在 創建 控 件 的 執行緒 中 訪問 該 控 件” 的 例外, 原因 就 是對 UI 控 件 的 操作 幾乎 都不 是 執行緒 安全 的( 部分 是), 一般 UI 控 件 只能 由 UI 執行緒 操作, 其余 的 所有 操作 均需 要 投遞 到 UI 執行緒 之中 執行, 否則 就會 像 前面 講過 的 那樣, 程式 出現 例外 或不 穩定,
可以 使用 Control. InvokeRequired 屬性 去 判斷 當前 執行緒 是否 是 創建 控 件 的 執行緒( UI 執行緒), 如果 是, 則 該 屬性 回傳 false, 可以 直接 操作 UI 控 件, 否則, 回傳 true, 不能 直接 操作 UI 控 件,
注: Control 含有 若干個 執行緒 安全 的 方法 和 屬性, 常見 的 主要 有 Control. InvokeRequired 屬性、 Control. Invoke 方法、 Control. BeginInvoke 方法( Control. Invoke 的 異步 版本)、 Control. EndInvoke 方法 以及 Control. CreateGraphics 方法, 這些 屬性 和 方法 都可以 在 非 UI 執行緒 中 使用, 并且 跨線 程 訪問 這些 方法 和 屬性 時不 會 引起 程式 例外,
2.8 呼叫與回呼 > 位置 661
.NET 平臺 開發 中的 回 調 主要 是 通過 委托 來 實作 的, 委托 是一 種 代理, 專門 負責 呼叫 方法,
2.9 托管資源與非托管資源 > 位置 666
.NET 中 物件 使 用到 的 非 托管 資源 主要 有 I/ O 流、 資料庫 連接、 Socket 連接、 視窗 句柄 等 各種 直接 與 作業系統 相關 的 資源,
2.13 協議 > 位置 751
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(1) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(1)](https://img.uj5u.com/2020/10/25/161444251815172.png)
圖 2- 11 網路 七 層 協議
第 3 章 編程之基礎:資料型別
3.1 參考型別與值型別 > 位置 844
通常 值 型別 又 分為 以下 兩個 部分,
(1) 簡單 值 型別: 包括 類似 int、 bool、 long 等. NET 內置 型別, 它們 本質上 也是 一種 結構 體,
(2) 復合 值 型別: 使用 Struct 關鍵字 定義 的 結構 體, 比如 System. Drawing. Point 等, 復合 值 型別 可以 由 簡單 值 型別 和 參考 型別 組成,
3.3 賦值與復制 > 位置 1118
參考 型別 的 賦值、 淺 復制、 深 復制 的 區別, 如圖 3- 15 所示,
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(2) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(2)](https://img.uj5u.com/2020/10/25/161444251815173.png)
值 型別 的 賦值、 淺 復制、 深 復制 的 區別, 如圖 3- 16 所示,
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(3) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(3)](https://img.uj5u.com/2020/10/25/161444251815174.png)
物件 深 復制 的 程序, 如圖 3- 17 所示,
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(4) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(4)](https://img.uj5u.com/2020/10/25/161444251815175.png)
3.3 賦值與復制 > 位置 1155
NET 中 可以 使用 “序列 化 和 反 序列 化” 的 技術 實作 物件 的 深 復制, 只要 一個 型別 及其 所有 成員 的 型別 都 標示 為 “ 可 序列 化”, 那么 就可以 先 序列 化 該類 型 物件 到 字 節流, 然后 再將 位元組 流 反序 列 化成 源 物件 的 副本, 這樣一來, 源 物件 與 副本 之間 沒有 任何 關聯, 從而 達到 深 復制 的 效果,
第 4 章 物以類聚:物件也有生命
4.1 堆和堆疊 > 位置 1235
堆疊 主要 用來 記錄 程式 的 運行 程序, 它有 嚴格 的 存盤 和 訪問 順序; 而 堆 主要 存盤 程式 運行 期間 產生 的 一些 資料, 幾乎沒有 順序 的 概念,
4.1 堆和堆疊 > 位置 1269
堆 跟 堆疊 的 本質 都是 一段 記憶體塊,
4.2 堆中物件的出生與死亡 > 位置 1272
堆疊 中的 物件 由 系統 負責 自動 存入 和 移 除, 正常 情況下, 跟我 們 程式 開發 的 關聯 并不 大,
4.2 堆中物件的出生與死亡 > 位置 1354
謹慎 使用 物件 的 析構方法, 析 構 方法 由 CLR 呼叫, 不受 程式控制, 而且 容易 造成 物件 重生, 析 構 方法 除了 用作 管理 非 托管 資源 外, 幾乎 不能 用作 其他 用途,
4.3 管理非托管資源 > 位置 1381
GC. SuppressFinalize 方法 請求 CLR 不要 再 呼叫 本 物件 的 析 構 方法, 原因 很 簡單, 既然 非 托管 資源 已經 釋放 完成, 那么 CLR 就 沒 必要 再繼續 呼叫 析 構 方法,
注: CLR 呼叫 物件 的 析 構 方法 是一 個 復雜 的 程序, 需要 消耗 非常 大的 性能, 這也 是 盡量 避免 在 析 構 方法 中 釋放 非 托管 資源 的 一個 重要 原因, 最好 是 徹底 地 不 呼叫 析 構 方法,
4.4 正確使用 IDisposable 介面 > 位置 1547
如果 一個 型別 使 用了 非 托管 資源, 或者 它 包含 使 用了 非 托管 資源 的 成員, 那么 開發者 就應 該 應用 “ Dispose 模式”: 正確地 實作( 間接 或 直接) IDisposable 介面, 正確地 重寫 Dispose( bool disposing) 虛 方法,
4.6 本章思考 > 位置 1587
呼叫 一個 物件 的 Dispose() 方法 后, 并不 意味著 該 物件 已經 死亡, 只有 GC 將對 象 實體 占用 的 記憶體 回收 后, 才 可以說 物件 已死, 但是 通常 情況下, 在調 用 物件 的 Dispose() 方法 后, 由于 釋 放了 該 物件 的 非 托管 資源, 因此 該 物件 幾乎 就 處于 “無用” 狀態,“ 等待 死亡” 是它 正確 的 選擇,
第 5 章 重中之重:委托與事件
5.1 什么是.NET 中的委托 > 位置 1625
像 宣告 一個 普通 方法 一樣, 提供 方法 名稱、 引數、 訪問 修飾 符 以及 回傳 值, 然后 在前面 加上 Delegate 關鍵字, 這樣 就 定義 了 一個 委托 型別,
委托 型別 定義 完成 后, 怎樣 去 實體 化 一個 委托 物件 呢? 其實 很 簡單, 跟 實體 化 其他 型別 物件 一樣, 我們 可以 通過 new 關鍵字 創建 委托 物件,
5.1 什么是.NET 中的委托 > 位置 1655
使用 委托 呼叫 方法 時, 我們 可以 直接 使用 “委托物件( 引數 串列);” 這樣 的 格式, 它 等效 于 “ 委托物件.Invoke( 引數 串列)”,
給 委托 賦值 的 另外 一種 方式 是: 委托物件 = 方法,
怎樣 讓 一個 委托 同時 呼叫 兩個 或者 兩個 以上 的 方法 呢? 答案 是 直接 使用 加法 賦值 運算子( =) 將 多個 方法 附加 到 委托 物件 上,
5.1 什么是.NET 中的委托 > 位置 1744
委托 內部 的 “鏈 表” 結構 跟 單向 鏈 表 的 實作 原理 卻不 相同, 它 并不是 通過 Next 參考 與 后續 委托 建立 關聯, 而是 將 所有 委托 存放 在 一個 陣列中, 如圖 5- 6 所示,
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(5) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(5)](https://img.uj5u.com/2020/10/25/161444251815176.png)
每一個 委托 型別 都有 一個 公開 的 GetInvocationList() 的 方法, 可以 回傳 已 附加 到 委托 物件 上 的 所有 委托, 即 圖 5- 6 中 陣列 串列 部分, 另外, 我們 平時 不 區分 委托 物件 和 委托 鏈 表, 提到 委托 物件, 它 很有可能 就 表示 一個 委托 鏈 表, 這 跟 單向 鏈 表 只 包含 一個 節點 時 道理 類似,
5.1 什么是.NET 中的委托 > 位置 1781
委托 跟 String 型別 一樣, 也是 不可改變 的, 換句話說, 一旦 委托 物件 創建 完成 后, 這個 物件 就不能 再被 更改, 那么 我們 前面 講到 的 將 一個 委托 附加 到 另外 一個 委托 物件 上 形成 一個 委托 鏈 表 又 該 如何 解釋 呢? 其實 這個 跟 String. ToUpper() 程序 類似, 我們 對 委托 進行 附加、 移 除 等 操作 都會 產生 一個 全新 的 委托, 這些 操作 并不 會 改變 原有 委托 物件,
5.1 什么是.NET 中的委托 > 位置 1821
框架 有兩 種 呼叫 框架 使用者 撰寫 的 代碼 的 方式, 一種 便是 面向 抽象 編程, 即 框架 中 盡量 不出 現 某個 具體 型別 的 參考, 而是 使用 抽象化 的 基 類 參考 或者 介面 參考 代替, 只要 框架 使用者 撰寫 的 型別 派生 自 抽象化 的 基 類 或 實作 了 介面, 框架 均可 以 正確地 呼叫 它們,
框架 呼叫 框架 使用者 代碼 的 另外 一種 方式 就是 使用 委托, 將 委托 作為 引數( 變數) 傳遞 給 框架, 框架 通過 委托 呼叫 方法,
5.2 事件與委托的關系 > 位置 1860
問題,. NET 中 提出 了 一種 介于 public 和 private 之間 的 另外 一種 訪問 級別: 在 定義 委托 成員 的 時候 給出 event 關鍵字 進行 修飾, 前面 加了 event 關鍵字 修飾 的 public 委托 成員, 只 能在 類 外部 進行 附加 和 移 除 操作, 而 呼叫 操作 只能 發生 在 型別 內部,
我們 把 類 中 設定 了 event 關鍵字 的 委托 叫作 “事件”,“ 事件” 本質上 就是 委托 物件, 事件 的 出現, 限制 了 委托 呼叫 只能 發生 在 一個 型別 的 內部, 如圖 5- 12 所示,
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(6) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(6)](https://img.uj5u.com/2020/10/25/161444251815177.png)
在 圖 5- 12 中 由于 server 中的 委托 使用 了 event 關鍵字 修飾, 因此 委托 只能 在 server 內部 呼叫, 對 外部 也 只能 進行 附加 和 移 除 方法 操作, 當 符合 某一 條件 時, server 內部 會 呼叫 委托, 這個 時間 不由 我們( client) 控制, 而是 由 系統( server) 決定, 因此 大部分 時候, 事件 在 程式 中 起 到了 回 調 作用,
呼叫 加了 event 關鍵字 修飾 的 委托 也稱 為 “ 激發事件”, 其中, 呼叫 方( 圖 5- 12 中的 server) 被稱為 “ 事件發布者”; 被 呼叫 方( 圖 5- 12 中的 client) 被稱為 “ 事件注冊者”( 或 “ 事件觀察者”、“ 事件訂閱者” 等, 本書 中統 一 稱之為 “事件 注冊 者”); 附加 委托 的 程序 被 稱之為 “ 注冊事件”( 或 “ 系結事件”、“ 監聽事件”、“ 訂閱事件” 等, 本書 中統 一 稱之為 “注冊 事件”); 移 除 委托 的 程序 被 稱之為 “ 注銷事件”, 通過 委托 呼叫 的 方法 被稱為 “ 事件處理程式”,
5.3 使用事件編程 > 位置 1940
在調 用 委托 鏈 時, 如果 某一個 委托 對應 的 方法 拋出 了 例外, 那么 剩下 的 其他 委托 將 不會 再 呼叫, 這個 很容易 理解, 本來 是按 先后 順序 依次 呼叫 方法, 如果 其中 某一個 拋出 例外, 剩下 的 肯定 會被 跳過, 為了 解決 這個 問題, 單單 是將 激發 事件 的 代碼 放在 try/catch 塊 中 是 不夠 的, 我們 還 需要 分 步調 用 每個 委托, 將 每 一步 的 呼叫 代碼 均 放在 try/catch 塊 中,
5.3 使用事件編程 > 位置 1967
除了 事件 本身 的 命名, 事件 所屬 委托 型別 的 命名 也 同樣 有 標準 格式, 一般以 “ 事件 名 EventHandler” 這種 格式 來給 委托 命名, 因此 前面 提到 的 NewEmailReceived 事件 對應 的 委托 型別 名稱 應該 是 “NewEmailReceivedEventHandler”), 激發 事件 時會 傳遞 一些 引數, 這些 引數 一般 繼承 自 EventArgs 型別( 后者 為. NET 框架 預定 義 型別), 以 “ 事件 名 EventArgs” 來 命名, 比如 前面 提到 的 NewEmailReceived 事件 在 激發 時 傳遞 的 引數 型別 名稱 應該 是 “NewEmailReceivedEventArgs”,
5.3 使用事件編程 > 位置 2004
之所以 要把 激發 事件 的 代碼 放在 一個 單獨 的 虛 方法 中, 是 為了 讓 從 該 型別( EmailManager) 派生 出來 的 子類 能夠 重寫 虛 方法, 從而 改變 激發 事件 的 邏輯,
虛 方法 的 命名 方式 一般 為 “ On 事件 名”, 另外, 該 代碼 中的 虛 方法 必須 定義 為 “protected”, 因為 派生 類 中 很可能 要 呼叫 基 類 的 虛 方法,
5.3 使用事件編程 > 位置 2039
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(7) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(7)](https://img.uj5u.com/2020/10/25/161444251815178.png)
圖 5- 13 屬性 和 事件 的 作用
5.4 弱委托 > 位置 2077
在 事件 編程 中, 委托 的 Target 成員, 就是 對 事件 注冊 者 的 強 參考, 如果 事件 注冊 者 沒有 注銷 事件, 強 參考 Target 便會 一直 存在, 堆 中的 事件 注冊 者 記憶體 就 一直 不 會被 CLR 回收, 這對 開發 人員 來講, 幾乎 是 很難 發覺 的,
像 “A a= new A();” 中的 a 被稱為 “ 顯式強參考( Explicit Strong Reference)”, 類似 委托 中 包含 的 不明顯 的 強 參考, 我們 稱之為 “ 隱式強參考( Implicit Strong Reference)”,
弱參考 與 物件 實體 之間 屬于 一種 “弱 關聯” 關系, 跟 強 參考 與 物件 實體 的 關系 不一樣, 就算 程式 中有 弱 參考 指向 堆 中 物件 實體, CLR 還是 會把 該 物件 實體 當作 回收 目標, 程式 中 使用 弱 參考 訪問 物件 實體 之前 必須 先 檢查 CLR 有沒有 回收 該 物件 記憶體, 換句話說, 當 堆 中 一個 物件 實體 只有 弱 參考 指向 它 時, CLR 可以 回收 它的 記憶體, 使用 弱 參考, 堆 中 物件 能否 被 訪問, 同時 掌握 在 程式 和 CLR 手中,
創建 一個 弱 參考 很 簡單, 使用 WeakReference 型別, 給 它的 構造 方法 傳遞 一個 強 參考 作為 引數 即可,
5.4 弱委托 > 位置 2105
在 編程 程序中, 由于 很難 管理 好強 參考, 從而 造成 不必 要的 記憶體 開銷, 尤其 前面 講到 的 “隱式 強 參考”, 在 使用 程序中 不易 發覺 它們 的 存在, 弱 參考 特別 適合 用于 那些 對 程式 依賴 程度 不高 的 物件, 即那 些 物件 生命 期 主要 不是 由 程式控制 的 物件, 比如 事件 編程 中, 事件 發布者 對 事件 注冊 者 的 存在 與否 不是 很 關心, 如果 注冊 者 在, 那就 激發 事件 并 通知 注冊 者; 如果 注冊 者 已經 被 CLR 回收 記憶體, 那么 就不 通知 它, 這 完全 不會 影響 程式 的 運行,
5.4 弱委托 > 位置 2109
前面 講到 過, 委托 包含 兩個 部分: 一個 Object 型別 的 Target 成員, 代表 被 呼叫 方法 的 所有者, 如果 方法 為 靜態 方法, 則 Target 為 null; 另一個 是 MethodInfo 型別 的 Method 成員, 代表 被 呼叫 方法, 由于 Target 成員 是 一個 強 參考, 所以 只要 委托 存在, 那么 方法 的 所有者 就會 一直 在 堆 中 存在 而 不 能被 CLR 回收, 如果 我們將 委托 中的 Target 強 參考 換成 弱 引 用的 話, 那么 不管 委托 存在 與否, 都不 會 影響 方法 的 所有者 在 堆 中 記憶體 的 回收, 這樣一來, 我們 在使 用 委托 呼叫 方法 之前, 需要 先 判斷 方法 的 所有者 是否 已經 被 CLR 回收, 我們 稱 將 Target 成員 換成 弱 參考 之后 的 委托 為 “ 弱委托”, 弱 委托 定義 代碼 如下:
//Code 5- 26 class WeakDelegate { WeakReference _weakRef; //NO. 1 MethodInfo _method; //NO. 2 public WeakDelegate(Delegate d) { _weakRef = new WeakReference(d.Target); _methodInfo = d.Method; } public object Invoke( param object[] args) { object obj = _weakRef.Target; if(_weakRef.IsAlive) //NO. 3 { return _method.Invoke(obj, args); //NO. 4 } else { return null; } } }
弱 委托 將 委托 與 被 呼叫 方法 的 所有者 之間 的 關 系由 “強 關聯” 轉換 成了 “ 弱 關聯”, 方法 的 所有者 在 堆 中的 生命 期 不再 受 委托 的 控制, 如圖 5- 16 所示, 為 弱 委托 的 結構,
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(8) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(8)](https://img.uj5u.com/2020/10/25/161444251815179.png)
本 小節 示例 代碼 中的 WeakDelegate 型別 并沒有 提供 類似 Delegate. Combine 以及 Delegate. Remove 這樣 操作 委托 鏈 表 的 方法, 當然 也 沒有 弱 委托 鏈 表 的 功能, 這些 功能 可以 仿照 單向 鏈 表 的 結構 去 實作, 把 每個 弱 委托 都 當作 鏈 表中 的 一個 節點, 其 方法 可 參照 5. 1. 2 小節 中 講到 的 單向 鏈 表,
5.5 本章回顧 > 位置 2151
委托 的 3 個 作用: 第一, 它 允許 把 方法 作為 引數, 傳遞 給 其他 的 模塊; 第二, 它 允許 我們 同時 呼叫 多個 具有 相同 簽名 的 方法; 第三, 它 允許 我們 異步 呼叫 任何 方法, 這 3 個 作用 奠定 了 委托 在. NET 編程 中的 絕對 重要 地位,
第 6 章 執行緒的升級:異步編程模型
6.1 異步編程的必要性 > 位置 2171
通常 情況下, 呼叫 一個 方法 都 符合 這樣 一個 規律: 呼叫 執行緒 開始 呼叫 方法 A 后, 在 A 回傳 之前, 呼叫 執行緒 得不到 程式 執行 的 控制 權, 也就是說, 方法 A 后面 的 代碼 是 不可能 執行 的, 直到 A 回傳 為止, 這種 呼叫 方式 被 稱之為 “ 同步呼叫”; 相反, 如果 呼叫 在 回傳 之前, 呼叫 執行緒 依舊 保留 控制 權, 能夠 繼續 執行 后面 的 代碼, 那么 這種 呼叫 方式 被稱為 “ 異步呼叫”,
同步 呼叫 也 被 一些 學者 稱為 “ 阻塞呼叫”; 一些 相對 的 異步 呼叫 則 被稱為 “ 非阻塞呼叫”,
6.2 委托的異步呼叫 > 位置 2202
理論上 講, 任何 一個 方法, 通過 委托 包裝 后, 都可以 實作 異步 呼叫,
.NET 編譯器 定義 的 每個 委托 型別 都 自動 生成 了 兩個 方法: BeginInvoke 和 EndInvoke, 這 兩個 方法 專門 用來 負責 異步 呼叫 委托,
BeginInvoke 回傳 一個 IAsyncResult 介面 型別, 它可 以 唯一 區分 一個 異步 呼叫 程序, BeginInvoke 一 執行 就能 馬上 回傳, 不會 阻塞 呼叫 執行緒, EndInvoke 表示 結束 對 委托 的 異步 呼叫, 但這 并不 意味著 它可 以 中斷 異步 呼叫 程序, 如果 異步 呼叫 還未 結束, EndInvoke 則 只能 等待, 直到 異步 呼叫 程序 結束, 另外, 如果 委托 帶有 回傳 值, 我們 必須 通過 EndInvoke 獲得 這個 回傳 結果,
6.2 委托的異步呼叫 > 位置 2233
委托 異步 呼叫 開始 后, 系統 會在 執行緒 池 中 找到 一個 空閑 的 執行緒 去 執行 委托,
6.2 委托的異步呼叫 > 位置 2296
異步 呼叫 委托 時, 由于 方法 實際 運行 在 其他 執行緒 中( 執行緒 池 中的 某一 執行緒, 非 當前 呼叫 執行緒), 因此 當前 執行緒 捕獲 不了 例外, 那么 我們 怎樣 才能 知道 異步 呼叫 程序中 到底 是否 會有 例外 呢? 答案 就在 EndInvoke 方法 上, 如果 異步 呼叫 程序 有 例外, 那么 該 例外 就會 在 我們 在 呼叫 EndInvoke 方法 時 拋出, 所以 我們 在 呼叫 EndInvoke 方法 時, 一定 要把 它 放在 try/catch 塊 中,
6.3 非委托的異步呼叫 > 位置 2358
.NET 中 提供 異步 方法 的 型別 有 Stream( 或 其 派生 類)、 Socket( 或 其 派生 類) 以及 訪問 資料庫 的 SqlConnection 型別 等, 它們 的 使用 方式 跟 委托 的 BeginInvoke 和 EndInvoke 方法 類似, 只是 命名 有所 差別, 基本上 都是 “Begin 操作” 和 “ End 操作” 這種 格式, 比如 FileStream. BeginRead 表示 開始 一個 異步 讀 操作, 而 FileStream. EndRead 則 表示 結束 異步 讀 操作,
6.6 本章思考 > 位置 2446
異步 編程 與 多 執行緒 編程 的 效果 類似, 都是 為了 能夠 并行 執行 代碼, 達到 同時 處理 任務 的 目的, 異步編程 時, 系統 自己 通過 執行緒池 來 分配 執行緒, 不需要 人工干預, 異步 編程 邏輯 復雜 不易 理解, 而 多 執行緒 編程 時, 完全 需要 人為 去 控制, 相對 較 靈活,
第 7 章 可復用代碼:組件的來龍去脈
7.1 .NET 中的組件 > 位置 2457
在. NET 編程 中, 我們 把 實作( 直接 或者 間接) System.ComponentModel.IComponent 介面 的 型別 稱為 “組件”,
7.1 .NET 中的組件 > 位置 2472
組件 和 控 件 不是 相等 的, 組件包含控制元件, 控 件 只是 組件 中的 一個 分類,
7.1 .NET 中的組件 > 位置 2477
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(9) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(9)](https://img.uj5u.com/2020/10/25/1614442518151710.png)
圖 7- 2 Windows Forms 中 控 件 之間 的 關系
所 有的 控 件 均 派生 自 Control 類, Control 類 又 屬于 組件, 因此 所有 控 件 均 具備 組件 的 特性,
不管 組件 還是 控 件, 它們 都是 可以 重復 使用 的 代碼 集合, 都 實作 了 IDisposable 介面, 都 需要 遵循 第 4 章 中 講到 的 Dispose 模式,如果 一個 型別 使 用了 非 托管 資源, 它 實作 IDisposable 介面 就可以 了, 那 為什么 還要 在. NET 編程 中 又 提出 組件 的 概念 呢?
這樣做 可以 說完 全是 為了 實作 程式 的 “ 可視化開發”, 也就是 我們 常說 的 “所見 即 所得”, 在 類似 Visual Studio 這樣 的 開發 環境 中, 一切 “ 組件” 均 可被 可 視 化 設計, 換句話說, 只要 我們 定義 的 型別 實作 了 IComponent 介面, 那么 在開 發 階段, 該 型別 就可以 出現 在窗 體 設計 器 中, 我們 就可以 使用 表單 設計 器 編輯 它的 屬性、 給 它 注冊 事件, 它 還能 被 表單 設計 器 中 別的 組件 識別,
7.2 容器 – 組件 – 服務模型 > 位置 2499
在. NET 編程 中, 把 所有 實作( 直接 或 間接) System. ComponentModel.IContainer 介面 的 型別 都 稱之為 邏輯 容器( 以下 簡稱 “容器”),
容器 是 為 組件 服務 的,
.NET 框架 中有 一個 IContainer 介面 的 默認 實作: System. ComponentModel.Container 型別, 該類 型 默認 實作 了 IContainer 介面 中的 方法 以及 屬性,
7.2 容器 – 組件 – 服務模型 > 位置 2514
傳統 容器 僅僅 是在 空間 上 簡單 地 將 資料 組織 在一起, 并不 能為 資料 之間 的 互動 提供 支持, 而 本章 討論 的 邏輯容器, 在 某種 意義上 講, 更為 高級, 它 能為 組件( 邏輯 元素) 之間 的 通信 提供 支持, 組件 與 組件 之間 不再 是 獨立 存在, 此外, 它 還能 直接 給 組件 提供 某些 服務,物理 容器 和 邏輯 容器 分別 與 元素 之間 的 關系, 如圖 7- 4 所示,
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(10) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(10)](https://img.uj5u.com/2020/10/25/1614442518151711.png)
物理 容器 中的 元素 之間 不能 相互 通信, 物理 容器 也不 可能 為 其內 部 元素 提供 服務; 邏輯 容器 中的 組件 之間 可以 通過 邏輯 容器 作為 橋梁, 進行 資料 交換; 同時, 邏輯 容器 還能 給 各個 組件 提供 服務, 所謂 服務, 就是 指 邏輯 容器 能夠 給 組件 提供 一些 訪問 支持, 比如 某個 組件 需要 知道 它的 所屬 容器 中共 包含 有 多少 個 組件, 那么 它 就 可以向 容器 發出 請求, 容器 收到 請求 后 會為 它 回傳 一個 獲取 組件 總數 的 介面,
在 本章 7. 1. 1 小節 中 我們 提 到過 IComponent 介面 中有 一個 ISite 型別 的 屬性, 當時 說 它是 起到 一個 “定位” 的 作用, 現在 看來, 組件 與 容器 之間 的 紐帶 就 是它, 組件 通過 該 屬性 與 它 所屬 容器 取得 了 聯系,
7.2 容器 – 組件 – 服務模型 > 位置 2574
Component、 Site 以及 Container 3 個 型別 均 包含 有 獲取 服務 的 方法 GetService, 現在 我們 可以 整理 一下 組件 向 容器 請求 服務 的 流程, 如圖 7- 6 所示,![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(11) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(11)](https://img.uj5u.com/2020/10/25/1614442518151712.png)
注: 容器 將 組件 添加 進來 時( 執行 Container. Add), 會 初始化 該 組件 的 Site 屬性, 讓 該 組件 與 容器 產生 關聯, 只有 當 這一 程序 發生 之后, 組件 才能 獲取 容器 的 服務,
7.2 容器 – 組件 – 服務模型 > 位置 2601
在 我們 向 表單 設計 器 中 拖動控制元件 時, 是 會 執行 類似 “new Button();” 這樣 的 代碼, 在 記憶體 中 實體化 一個 組件 實體,
7.2 容器 – 組件 – 服務模型 > 位置 2655
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(12) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(12)](https://img.uj5u.com/2020/10/25/1614442518151713.png)
圖 7- 10 表單 設計 器 中的 組件 與 生成 的 源 代碼
在 圖 7- 10 中, 圖中 左邊 顯示 我們 拖放 到 設計 器 中的 一個 Button 控 件, 在 這個 程序中, 表單 設計 器 除了 會 實體 化 一個 Button 控 件( 圖中 左邊 Form2 中), 還會 為我 們 生成 右邊 的 代碼,
7.3 設計時與運行時 > 位置 2672
任何 組件 都有 兩種 狀態: 設計時 和 運行時,
判斷 組件 的 當前狀態 有 以下 兩種 方法,
(1) 判斷 組件 的 DesignMode 屬性, 每個 組件 都有 一個 Bool 型別 的 DesignMode 屬性, 正如 它的 字面 意思, 如果 該 屬性 為 true, 那么 代表 組件 當前 處于 設計 時 狀態; 否則 該 組件 處于 運行時 狀態,
(2) 隨便 請求 一個 服務, 看 回傳 來的 服務 介面 是否 為 null, 前面 提 到過, 當 一個 組件 不屬于 任何 一個 容器 時, 那么 它 通過 GetService 方法 請求 的 服務 肯定 回傳 為 null,
注:(1)(2) 方法 均不 適合 嵌套組件, 因為 表單 設計 器 只會 將 最外 層 組件 的 DesignMode 屬性 值 設定 為 true,
有 一種 可以 解決 嵌套 組件 中 無法 判斷 其 子 組件 狀態 的 方法, 那就 是 通過 Process 類 來 檢查 當前 行程 的 名稱, 看 它是 否 包含 “devenv” 這個 字串, 如果 有, 那么 說明 組件 當前 處于 Visual Studio 開發 環境 中( 即 組件 處于 設計 時), if( Process. GetCurrentProcess (). ProcessName. Contains(”devenv”)) 為 假, 說明 組件 處于 運行時, 這種 方法 也有 一個 弊端, 很 明顯, 如果 我們 使用 的 不是 Visual Studio 開發 環境( 即 行程 名 不 包含 devenv), 或者 我們自己 的 程式 行程 名稱 本身 就 已經 包含 了 devenv, 那么 該 怎么辦 呢?
在開 發 一些 需要 授權 的 組件 時, 就可以 用到 組件 的 兩種 狀態, 這些 需要 授權 的 組件 收費 物件 一般 是 開發者, 因此, 在 開發者 使用 這些 組件 開發 系統 的 時候( 處于 開發 階段), 就應 該有 授權 入口, 而 當 程式 運行 之后, 就不 應該 出現 授權 的 界面,
7.4 控制元件 > 位置 2776
無論是 復合 控 件、 擴展 控 件 還是 自定義 控 件, 我們 均可 以 重寫 控 件 的 視窗程序: WndProc 虛 方法, 從 根源 上 接觸 到 Windows 訊息, 這個 做法 并不是 自定義 控 件 的 專利,
第 8 章 經典重視:桌面 GUI 框架揭秘
8.2 Win32 應用程式的結構 > 位置 2841
程式 是 無法 直接 識別 用戶 鍵盤 或者 滑鼠 等 設備 的 輸入 資訊, 這些 輸入 必須 先由 作業系統 轉換 成 固定 格式 資料 之后, 才能 被 程式 使用,
在 Windows 編程 中, 我們 把 由 作業系統 轉換 之后 的 固定 格式 資料 稱為 Windows 訊息, Windows 訊息 是一 種 預 定義 的 資料 結構( 比如 C 中的 Struct), 它 包含 有 訊息 型別、 訊息 接收者 以及 訊息 引數 等 資訊, 我們 還把 程式 中 獲取 Windows 訊息 的 結構 稱之為 Windows 訊息回圈, Windows 訊息 回圈 在 代碼 中就 是一 個 回圈 結構( 比如 while 回圈), 它 不停 地 從 作業系統 中 獲取 Windows 訊息, 然后 交給 程式 去 處理,
8.2 Win32 應用程式的結構 > 位置 2895
在 Windows 中, 其實 將 訊息 分成 了 兩類, 一類 需要 存入 訊息 佇列, 然后 由 訊息 回圈 取出 來之 后才 能被 視窗 程序 處理, 這類 訊息 被 稱之為 “ 佇列訊息”( Queued Message), 這類 訊息 主要 包括 用戶 的 滑鼠 鍵盤 輸入 訊息、 繪制 訊息 WM_ PAINT、 退出 訊息 WM_ QUIT 以及 時間 訊息 WM_ TIMER, 另 一類 是 不需要 存入 訊息 佇列, 也不 經過 訊息 回圈, 它們 直接 傳遞 給 視窗 程序, 由 視窗 程序 直接 處理, 這類 訊息 被 稱之為 “ 非佇列訊息”( Nonqueued Message), 當 作業系統 想要 告訴 視窗 發生了 某 件事 時, 它 會 給 視窗 發送 一個 非 佇列 訊息, 比如 當 我們 使用 SetWindowPos API 移動 視窗 后, 系統 自動 會 發送 一個 WM_ WINDOWPOSCHANGED 訊息 給 該 視窗 的 視窗 程序, 告訴 它 位置 發生 變化 了,
8.4 Windows Forms 框架 > 位置 3148
在 Windows Forms 框架 中, 以 Control 為 基 類, 其他 所有 與 表單 顯示 有關 的 控 件 幾乎 都 派生 自 它; Control 類 中的 WndProc 虛 方法 就是 我們 在 Win32 開發 模式 中 所 熟悉 的 視窗 程序, 另外, 前面 也 講到 過, 表單 和 控 件 本質上 是一 個 東西, 只是 它們 有著 不同 的 屬性, 所以 我們 可以 看到, 表單 類 Form 間接 派生 自 Control 類,
Winform 程式 中 包含 3 個 部分: 訊息 佇列、 UI 執行緒 以及 控 件,
8.5 Winform 程式的結構 > 位置 3180
在 每個 Winform 程式 的 Program. cs 檔案 中, 都有 一個 Main 方法, 該 Main 方法 就是 程式 的 入口 方法, 每個 程式 啟動 時 都會 以 Main 方法 為 入口, 創建 一個 執行緒, 這個 執行緒 就是 UI 執行緒, 可能 你會 問 UI 執行緒 怎么 沒有 訊息 回圈 呢? 那是 因為 Main 方法 中 總是 會 出現 一個 類似 Application. Run 的 方法, 而 訊息 回圈 就 隱 藏在 了 該 方法 內部( 具體 參見 下一 小節 內容), 一個 程式 理論上 可以 有 多個 UI 執行緒, 且 每個 執行緒 都有 自己的 訊息 佇列( 由 作業系統 維護)、 訊息 回圈、 表單 等 元素, 如圖 8- 13 所示,
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(13) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(13)](https://img.uj5u.com/2020/10/25/1614442518151714.png)
由于 UI 執行緒 之間 的 資料 交換 比較 復雜, 因此 在 實際 開發 中, 在 沒有 特殊 需求 的 情況下, 一個 程式 一般 只 包含 有一個 UI 執行緒,
8.5 Winform 程式的結構 > 位置 3265
對 UI 執行緒 的 認識:
① 一個 程式 可以 包含 有 多個 UI 執行緒, 我們 完全可以 通過 System. Threading. Thread 類 新創 建 一個 普通 執行緒, 然后 在 該 執行緒 中 呼叫 Application. Run 方法 來 開啟 訊息 回圈;
② 一個 UI 執行緒 結束( 該 執行緒 中的 訊息 回圈 個數 為 “0”) 后, 將會 激發 Application. ThreadExit 事件, 告知 有 UI 執行緒 結束, 只有 當 所有 UI 執行緒 都 結束( 程式 中 訊息 回圈 總數 為 “ 0”), 才會 激發 Application. Exit 事件, 告知 應用 程式 退出,
最后 我們 再來 看一下 Windows Forms 中 訊息 回圈 的 結構圖, 如圖 8- 14 所示,
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(14) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(14)](https://img.uj5u.com/2020/10/25/1614442518151715.png)
8.5 Winform 程式的結構 > 位置 3319
Windows Forms 框架 將 表單 和 視窗 程序 封 裝在 了 一起, Control( 或 其 派生 類, 下同) 類 中的 WndProc 虛 方法 就是 控 件 的 視窗 程序, 之所以 將 視窗 程序 宣告 為 虛 方法, 這是 因為 Windows Forms 框架 是 面向 物件 的, 它 充分 地利 用了 面向 物件 編程 中的 “多 態” 特性, 因此, 所有 Control 類 的 派生 類 均可 以 重寫 它的 視窗 程序, 從而 從 源頭 上 攔截 到 Windows 訊息, 處理 自己 想要 處理 的 Windows 訊息,
視窗 程序 做了 一個 非常 重要的 事: 將 Window 訊息 轉換 成了. NET 中的 事件,
第 9 章 溝通無礙:網路編程
9.1 兩種 Socket 通信方式 > 位置 3518
(TCP)資料 是按 順序 走在 建立 的 一條 隧道 中, 那么 資料 就應 該 遵循 “先走 先 到達” 的 規則, 并且 隧道 中的 資料 以 “ 流” 的 形式 傳輸, 發送 方 發送 的 前后 兩次 資料 之間 沒有 邊界, 需要 接收 方自 己 根據 事先 規定 好的 “ 協議” 去 判斷 資料 邊界,
9.1 兩種 Socket 通信方式 > 位置 3555
UDP 通信 中, 資料 是以 “數 據報” 的 形式 傳輸, 以 一個 整體 發送、 以 一個 整體 接收, 因此 UDP 存在 資料 邊界, 但是 UDP 接 收到 的 資料 是 無序 的, 先 發送 的 可能 后接 收, 后 發送 的 可能 先 接收, 甚至 有的 接收 不到,
9.1 兩種 Socket 通信方式 > 位置 3605
在. NET 中 有關 Socket 通信 編程 的 型別 主要 有 5 種, 見表 9- 1,
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(15) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(15)](https://img.uj5u.com/2020/10/25/1614442518151716.png)
TcpListener 和 TcpClient 的 關系 如圖 9- 9 所示
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(16) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(16)](https://img.uj5u.com/2020/10/25/1614442518151717.png)
圖 9- 9 中, TcpListener 偵聽 來自 客戶 端 的 “連接” 請求, 回傳 一個 代理 TcpClient, 該 代理 與 客戶 端 的 TcpClient 進行 資料 交換,
UdpClient 在 UDP 通信 中 所處 的 角色 如圖 9- 10 所示:
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(17) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(17)](https://img.uj5u.com/2020/10/25/1614442518151718.png)
NetworkStream 型別 是 System. IO. Stream 類 的 一個 派生 類,NetworkStream 只能 用于 TCP 通信 中,
Socket 型別 是一 個 相對 較 基礎 的 通信 型別, 它 既能 實作 TCP 通信 也能 實作 UDP 通信, 可以 認為 TcpListener、 TcpClient 以及 UdpClient 進一步 封裝 了 Socket 型別,
9.3 UDP 通信的實作 > 位置 3840
之所以 將 TCP 通信 中 應用 層 協議 的 資料 結構設計 成 位元組流 的 形式, 是因為 TCP 通信 中 資料 是以 流的 形式 傳輸, 以 位元組 流的 形式 格式化 資料 后, 更 方便 程式 判斷 資料 邊界,
而在 UDP 通信 中, 資料 以 資料 報的 形式 傳輸, 每次 接 收到 的 資料 是 完整 的, 對于 當前 局域網 即時 通信 實體 來講, 協議 使用 文本 的 形式 更 方便 程式 處理 資料, 當然, 我們 完全 也可以 將 UDP 通信 中 應用 層 協議 的 資料 結構設計 成 位元組 流的 形式,
9.4 異步編程在網路編程中的應用 > 位置 3991
使用 Socket. BeginSendTo() 方法 開始 一個 異步 發送 程序, 并為 該 方法 提供 一個 AsyncCallback 的 回 調 引數, 該 方法 的 呼叫 不會 阻塞 呼叫 執行緒, 我們 在 回 調 方法 OnSend 中 可使用 Socket. EndSendTo() 方法, 結束 異步 發送 程序, 該 方法 回傳 實際 發送 的 資料 長度,
9.4 異步編程在網路編程中的應用 > 位置 4011
異步 編程 也能 實作 回圈 接收 資料, 但卻 看 不到 顯 式 地 創建 的 執行緒, 也 看不 到 類似 while 這樣 的 回圈 陳述句,
9.6 本章思考 > 位置 4062
所有 的 通信協議 本質上 都是 一種 資料結構, 通信 雙方 都 必須 按照 這種 資料 結構 規定 的 形式 去 發送 或 接收( 決議) 資料,
基于 TCP 協議 的 通信 在 進行 資料 互動 之前 需要 先 建立 連接, 類似 打電話, 這種 通信 方式 保證 了 資料 傳輸 的 正確性、 可靠性, 基于 UDP 協議 的 通信 在 進行 資料 傳輸 之前 不需要 建立 連接, 類似 發 短信, 這種 通信 方式 不能 保證 資料 傳輸 的 正確性,
第 10 章 動力之源:代碼中的 “泵”
10.2 常見的 “泵” 結構 > 位置 4150
桌面 程式 的 UI 執行緒 中 包含 一個 訊息 回圈( 確切 地說, 應該 是 While 回圈), 該 回圈 不斷 地 從 訊息 佇列 中 獲取 Windows 訊息, 最終 通過 呼叫 對應 的 視窗 程序, 將 Windows 訊息 傳遞 給 視窗 程序 進行 處理,
10.2 常見的 “泵” 結構 > 位置 4179
瀏覽器 每次 發送 http 請求 時, 都 必須 與 Web 服務器 建立 連接, Web 服務器 端 請求 處理 結束 后, 連接 立刻 關閉, 瀏覽器 下一 次 發送 http 請求 時, 必須 再一次 重新 與 服務器 建立 連接, 由此可見, 我們 所說 的 HTTP 協議 是 面向 無 連接 的, 具體 指 Web 服務器 一次 連接 只 處理 一個 請求, 請求 處理 完畢 后, 連接 關閉, 瀏覽器 在前 一次 請求 結束 到下 一次 請求 開始 之前 這段 時間, 它是 處于 “斷開” 狀態 的, 因此 稱 HTTP 協議 是 “ 無連接” 協議,
Web 服務器 除了 跟 瀏覽器 之間 不會 保持 持久 性的 連接 之外, 它 也不 會 保存 瀏覽器 的 狀態, 也就是說, 同一 瀏覽器 先后 兩次 請求 同一個 Web 服務器, 后者 不會 保留 第一次 請求 處理 的 結果 到 第二次 請求 階段; 如果 第二次 請求 需要 使用 第一次 請求 處理 的 結果, 那么 瀏覽器 必須 自己 將 第一次 的 處理 結果 回 傳到 服務器 端,
第 11 章 規繩矩墨:模式與原則
11.1 軟體的設計模式 > 位置 4308
程式 的 運行 意味著 模塊 與 模塊 之間、 物件 與 物件 之間 不停 地 有數 據 交換, 觀察者模式 要 強調 的 是, 當 一個 目標 本身 的 狀態 發生 改變( 或者 滿足 某一 條件) 時, 它 會 主動 發出通知, 通知 對 該 變化 感興趣 的 其他 物件, 將 通知者 稱為 Subject( 主體), 將被 通知者 稱為 Observer( 觀察者),
11.1 軟體的設計模式 > 位置 4358
“觀察者 模式” 是 所有 框架 使用 得 最 頻繁 的 設計 模式 之一, 原因 很 簡單,“ 觀察者 模式” 分隔 開了 框架 代碼 和 框架 使用者 撰寫 的 代碼, 它是 “ 好萊塢原則”( Don\’ t call us, we will call you) 的 具體 實作 手段, 而 “好萊塢 原則” 是 所有 框架 都要 嚴格遵守 的,
11.1 軟體的設計模式 > 位置 4361
在 Windows Forms 框架 中, 可以說 “觀察者 模式” 無處不在, Windows Forms 框架 中的 “ 觀察者 模式” 不是 通過 “ 介面 – 具體” 這種 方式 去 實作 的, 而是 更多 地 通過 使用. NET 中的 “ 委托 – 事件” 去 實作, 這 在 第 8 章 講 Winform 程式 結構 時 已經 有所 說明, 比如 控 件 處理 Windows 訊息 時, 最終 是以 “事件” 的 形式 通知 事件 注冊 者, 那么 這里 的 事件注冊者 就是 觀察者 模式 中的 “ 觀察者”, 控制元件 就是 觀察者 模式 中的 “ 主體”,
可以 認為, 事件 的 發布者 等于 觀察者 模式 中的 “主體”( Subject), 而 事件 的 注冊 者 等于 觀察者 模式 中的 “ 觀察者”,
11.1 軟體的設計模式 > 位置 4397
根據 各種 設計 模式 的 作用, 將 常見 的 23 種 設計模式 分為 3 大類, 見表 11- 1,
![[讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(18) [讀書筆記] 《修煉之道:.NET 開發要點精講》插圖(18)](https://img.uj5u.com/2020/10/25/1614442518151719.png)
11.5 本章思考 > 位置 4605
五大原則 及 英文 全稱 分別 如下,
① 單一 職責 原則( Single Responsibility Principle),
② 開閉 原則( Open Closed Principle),
③ 里 氏 替換 原則( Liskov Substitution Principle),
④ 介面 隔離 原則( Interface Segregation Principle),
⑤ 依賴 倒置 原則( Dependency Inversion Principle),
第 12 章 難免的尷尬:代碼依賴
12.1 從面向物件開始 > 位置 4738
類繼承 強調 “我是( Is- A)” 的 關系, 派生 類 “ 是” 基 類( 注意 這里 的 “ 是” 代表 派生 類 具備 基 類 的 特性), 而 介面繼承 強調 “我 能做( Can- Do)” 的 關系, 實作 了 介面 的 型別 具有 介面 中 規定 的 行為 能力( 因此 介面 在 命名 時 均以 “ able” 作為 后綴),
12.1 從面向物件開始 > 位置 4743
在使 用 繼承 時, 應 遵循 以下 準則,
(1) 嚴格遵守 “里 氏 替換 原則”, 即 基 類 出現 的 地方, 派生 類 一定 可以 出現, 因此, 不要 盲目 地 去使 用 繼承, 如果 兩個 類 沒有 衍生 的 關系, 就不 應該 有 繼承 關系,
(2) 由于 派生 類 會 繼承 基 類 的 全部 內容, 所以 要 嚴格控制 好 型別 的 繼承 層次, 不然 派生 類 的 體積 會 越來越大, 另外, 繼承 是 增加 耦合 的 最重要 因素, 基 類 的 修改 必然會 影響 到 派生 類,
(3) 繼承 強調 型別 之 間的 通 性, 而非 特性, 因此 一般 將 型別 都 具有 的 部分 提取 出來, 形成 一個 基 類( 抽象 類) 或者 介面,
12.2 不可避免的代碼依賴 > 位置 4815
為了 衡量 物件 之間 依賴 程度 的 高低, 引進 了 “ 耦合” 這一 概念, 耦合 度 越高, 說明 物件 之間 的 依賴 程度 越高, 為了 衡量 物件 獨立 性的 高低, 引進 了 “ 內聚” 這一 概念, 內聚性 越高, 說明 物件 與外 界 互動 越少, 獨立性 越 強, 很 明顯, 耦合 與 內 聚 是 兩個 相互 對立 又 密切相關 的 概念,
12.3 降低代碼依賴 > 位置 4936
除了 上面 說到 的 將相 同 部分 提取 出來 放到 一個 介面 中, 有時候 還需 要將 相同 部分 提取 出來, 生成 一個 抽象化 的 基 類, 如 抽象類, 介面 強調 相同 的 行為, 而 抽象 類 一般 強調 相同 的 屬性, 并且 要使 用在 有 族群 層次 的 型別 設計 中,
12.3 降低代碼依賴 > 位置 4987
通過 屬性 產生 的 依賴 關系(屬性注入) 比較 靈活, 它的 有效期 一般 介于 “構造 注入” 和 “ 方法 注入” 之間,
在 很多 場合, 3 種 依賴注入 的 方式 可以 組合 使用, 即 可以 先 通過 “構造 注入” 讓 依賴 者 與 被 依賴 者 產生 依賴 關系, 后期 再 使用 “ 屬性 注入” 的 方式 更改 它們 之間 的 依賴 關系,“ 需要 注意 的 是, 依賴 注入” 是以 “ 依賴 倒置”” 為 前提 的,
12.4 框架的 “代碼依賴” > 位置 4998
注:“ 控制轉換、 依賴倒置 以及 依賴注入 是 3 個 不同 性質 的 概念,“ 控制轉換” 強調 程式控制 權 的 轉移, 注重 軟體 運行 流程;“ 依賴倒置” 是一 種 降低 代碼 依賴 程度 的 理論 指導思想, 它 注重 軟體 結構;“ 依賴注入” 是對 象之 間 產生 依賴 關系 的 一種 具體 實作 方式, 它 注重 編程 實作,
“控制 轉換” 又稱 “ 好萊塢 原則”, 它 建議 框架 與 開發者 撰寫 代碼 之間 的 關系 是 Don\’ t call us, we will call you, 即 整個 程式 的 主動權 在 框架 手中,
12.6 本章思考 > 位置 5022
“依賴 倒置 原則” 中的 “ 倒置” 二字 作 何 解釋?
A: 正常 邏輯思維 中, 高層 模塊 依賴 底層 模塊 是 天經地義、 理所當然 的, 而 “依賴 倒置 原則” 建議 我們 所有 的 高層 模塊 不應該 直接 依賴于 底層 模塊, 而 都 應該 依賴于 一個 抽象, 這里 的 “ 倒置” 二字 并不是 “ 反過來” 的 意思( 即 底層 模塊 反過來 依賴于 高層 模塊), 它 只是 說明 正常 邏輯思維 中的 依賴 順序 發生了 變化, 把 所有 違背 了 正常 思維 的 東西 都 稱之為 “ 倒置”,
原創文章,轉載請注明: 轉載自 獨立觀察員?博客
本文鏈接地址: [讀書筆記] 《修煉之道:.NET 開發要點精講》[http://dlgcy.com/dotnet-program-key-point/]
微信公眾號
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/190590.html
標籤:.NET技术
