[.NET] 關於晚期繫結 (Late Binding)

晚期繫結是一種程式語言與作業系統的手法,用意在於避免因為編譯時期的型別檢查機制,導致程式員在編寫程式時,需要處理過多的型別資訊 (Type Information),晚期繫結可以有效的處理在平台之間型別資訊的隔離,讓編譯出來的程式可以在特定的平台之間執行,而不需要被型別資訊綁住,不過也不能過度濫用,除非原生平台就是要用晚期繫結 (例如 JavaScript)。

晚期繫結 (Late Binding) 在物件導向程式語言中占有一席地位,與它相對的早期繫結 (Early Binding) 是編譯型語言如 C#, Java, VB 等編譯器處理型別檢查的作法,它們兩個的差異在於程式的執行期 (Runtime) 對於變數型別處理方式的不同。

早期繫結的作法在程式執行時,程式載入器 (Loader) 會將變數的型別一起讀進去,並且依照型別的類型決定它會放在 stack (實值變數) 或是 heap (參考變數),用早期繫結所產生的變數,由於其型別資訊都已經載入記憶體,所以程式在存取時不必考慮變數的型別,直接於記憶體中讀取型別資訊就能操作變數,不必額外的檢查工作。

「.NET stack heap」的圖片搜尋結果

晚期繫結則是在程式執行時,程式載入器無法確定變數的型別,最常見的就是以 object 為型別名稱宣告的變數,程式載入器會自動將這種變數直接放到 heap,當程式需要取出變數時,要嘛是使用 object 型別,要嘛就要轉型 (Type Casting),轉型後的變數就會是正確的型別,這是一般的程式語言的作法。Visual Basic (不是 VB6) 在設計的初期其實也是用這種作法,然而為了要相容於 VB6 的特性 (晚期繫結是 VB6 的特性之一),故在 VB.NET 中,若變數是以 object 為型別宣告的變數,VB.NET 會自動處理型別轉換的工作,這個叫做隱含型別轉換 (Implicit Type Casting),然而這對效能是很傷的,就連 VB6 的時代,微軟都建議 VB6 的程式員在程式最頂端加上 Option Strict On,以關閉隱含型別轉換功能。

註:你可能也有在 C# 看過這個詞,但若用 C# 做到隱含型別轉換,要由類別端實作 implicit operator 才可以,否則只能用明確型別轉換。

晚期繫結還有一個特點,就是系統提供的型別不夠明確時,晚期繫結有助於簡化複雜型別的處理,最典型的例子就是 Office 的 COM APIs,COM 為了要達到二進位的相容性 (Binary Compatibility),它採用了 vtable (虛擬函式表) 的作法,型別資訊由 vtable 提供,程式語言本身並未提供型別資訊。

「vtable」的圖片搜尋結果

COM Automation 則另提供了不同的機制來支援不同的程式語言作法,但與 vtable 類似的是,程式語言仍然不會提供型別資訊,而改由型別函式庫 (Type Library) 來提供,VB 本身的晚期繋結特性 (尤其是 VBA) 就很適合這種型別不確定的環境,在用 VBA 開發程式時,就算不明確宣告型別,VBA 還是可以找到正確的型別和方法來呼叫 (除非給予與型別和方法是錯的), 但若是要叫 C# 來做這件事可就難了,這也是為什麼之後要引進 dynamic 的能力給 C# 的其中一個原因。

晚期繫結在複雜型別系統中能有效簡化程式語言對型別的處理,經過前面的討論,相信你一定能了解這一點,但問題也在於這個優點,俗話說有一好就沒兩好,晚期繫結的變數是以 object 這種鬆散的型別資訊存在於程式的 heap 裡面,程式要處理它之前都要經過轉型,在 .NET 裡面,object 轉成指定型別的作業叫做 unboxing,CLR 必須要做很多事情確認變數的合法性,才能轉型成指定的變數,否則就會擲回 InvalidCastException 例外,問題不在轉型失敗,而是在要做很多事情確認變數合法性這件事,早期繫結則不用做這件事,所以速度上早期繫結一定比晚期繫結要快。

晚期繫結有它適合的舞台,將它用在正確的地方可以讓程式的處理更有彈性,不過在一般的情況下,早期繫結仍優於晚期繫結,所以還是盡量以早期繫結的作法為主吧,寫 VB 的你也建議養成在程式開頭加上 Option Strict On 指令的習慣,以避免過份依賴晚期繫結,久了真的對程式能力有害喔。

References:

C# 轉型與類型轉換

Visual Basic 轉型與類型轉換

早期與晚期繫結