命名空間的作用
命名空間的核心價值很簡單:幫型別分組,降低命名衝突,讓程式碼更容易定位。
例如:
using System.IO;
using System.Text;
這兩行不是把型別「載入」進來,而是告訴編譯器:之後在解析型別名稱時,可以優先到這些命名空間裡找。
編譯器如何解析型別名稱
假設程式碼裡寫了:
FileStream stream;
StringBuilder builder;
編譯器會嘗試根據目前作用域、using 指示詞,以及完整限定名稱來解析 FileStream 和 StringBuilder。
如果前面有:
using System.IO;
using System.Text;
那它就能分別找到:
System.IO.FileStreamSystem.Text.StringBuilder
命名空間的功能,本質上就是幫助名稱解析,而不是改變型別本身。
名稱衝突與完整限定名
如果兩個命名空間內剛好都有同名型別,就需要明確指定:
A.Hello first = new A.Hello();
B.Hello second = new B.Hello();
只寫 Hello 時,編譯器無法判斷你要哪一個型別;寫成完整限定名後,歧義就消失了。
這也是為什麼大型專案裡,命名空間規劃不能太隨意。只靠「型別名稱取好一點」通常不夠。
別名
如果完整限定名太長,或者衝突太頻繁,可以給命名空間或型別起別名:
using WinForms = System.Windows.Forms;
之後就能這樣寫:
WinForms.Form form = new WinForms.Form();
global:: 也很常用,它表示從全域命名空間根節點開始解析:
global::Configuration config = new global::Configuration();
當專案內部又宣告了同名命名空間或型別時,這種寫法特別有用。
另外還有 extern alias,用來處理不同程序集之間的同名型別衝突:
extern alias TestAlias;
這通常只會在比較複雜的程序集引用場景出現,但知道它的存在很重要。
命名空間與程序集不是一回事
初學時很容易把命名空間和程序集混在一起,但它們其實是兩個概念:
- 命名空間:邏輯分組
- 程序集:編譯與部署單位
它們不是一對一關係:
- 一個程序集可以包含很多命名空間
- 同一個命名空間也可以散落在多個程序集裡
所以看到 MyCompany.Tools.IO 這樣的命名空間名稱,不代表它一定只存在於某一個 DLL 裡。
小結
命名空間主要解決的是名稱管理問題,而不是執行期問題。整理起來有三個重點:
using影響的是名稱解析,不是型別載入- 發生命名衝突時,可以改用完整限定名或別名
- 命名空間和程序集彼此相關,但不是同一件事
把這個層次分清楚,之後讀大型程式碼庫會輕鬆很多。