程式世界有兩種神秘的元素,它們無處不在,卻常常未被察覺。它們一動一靜,卻又和諧相處。我給這對兄弟取上不太恰當的名字,一個叫“協議”,一個叫“約束”。我們常常看到的動態語言、靜態語言背後,本質上就是“協議”與“約束”兩種元素的作用。
C#是一門優美且處於快速發展中的語言,它融合了靜態和動態優勢,如果運用得當,必能動靜結合,呈現出一種和諧之美。本篇是C#動靜結合編程的第一篇,希望這個系列能和大家一起探討如何在C#中最大限度的發揮動靜結合的潛能。
本人還只是C#初學者,對電腦理論的理解還很膚淺,文中錯誤歡迎批評指正,不足之處歡迎補充,謝謝!
PS:不加特殊說明時,本文提到的"動態"或“協議”是指程式的運行時協議,包括但不限於動態類型;“靜態”或“約束”是指程式的編譯時間文法約束,而語義約束歸為協議一類。
被忽略的協議
談到“協議”,最先浮現在我們腦海中的可能是TCP/IP協議棧,但其實我們隨處都在和協議打交道。下面的例子,你看出協議來了嗎?
B(){
ArrayList lst = A();
foreach (string item in lst){
Console.WriteLine(item.Length);
}
}
方法B假定方法A遵守:返回的ArrayList內部都是string類型的元素。這就是它們之間的協議,這個協議不受編譯器靜態檢查的約束。所以,協議意味著運行時的不確定性,方法A完全可能在返回結果中裝入非string類型的元素,而這將導致B在運行時產生異常。
.NET2.0通過泛型集合增加了靜態類型約束:
B(){
List<string> lst = A();
foreach (string item in lst){
Console.WriteLine(item);
}
}
這樣,B再也不用擔心lst內部存在非string類型的元素了,一切得益於泛型為A加上的靜態類型約束。
約束有強弱
約束有強弱之分。越強的約束越安全,靜態性越強,受編譯器的支援越大;反之,越弱的約束,動態性越強,運行時靈活性越大。
介面和委託都是用於分離規範與實現從而達到面向抽象編程的重要手段,二者有共通之處,尤其單方法介面和委託更是如此。常常看到關於單方法介面和委託異同的討論,不少朋友認為它們完全等價。其實,它們有明顯不同的文法約束強度。介面是靜態類型約束,而委託只是靜態簽名約束,二者的強度完全不同。換句話說,委託具有更多的協議性,只要是符合簽名的方法或匿名委託,都可以被委託調用,而能被介面調用的對象必須實現該介面。
來看一個例子:需要編寫一個類A,其內部需要日誌功能;A採用IoC方式,不依賴於具體的Logger類,由使用者根據需要注入具體的實現;同時,A的使用者B,希望採用第三方的Logger類。
a. 基於介面的IoCinterface ILogger { void Write(string msg); }
class A{
ILogger Logger { get; set; }
void F() {}
}
class B{
G(){
A a = new A();
a.Logger = new LogAdapter(); //注入依賴
a.F();
}
}
//對第3方Logger進行封裝
class LogAdapter : ILogger{
Write(string msg){//這裡調用第3方的Logger類}
}
b. 基於委託的IoCclass A{
Action<string> Logging { get; set; }
void F() {}
}
class B{
G(){
A a = new A();
a.Logging = delegate(string msg){ //調用第3方Logger類 };
a.F();
}
}
比較上面兩個例子,我們就會發現委託比介面的約束要弱得多,使用起來靈活得多。基於介面的實現不得不增加一個Adapter去機械地適應介面的類型約束,而基於委託的實現只需要保證方法簽名約束即可。
上面討論介面與委託的文法約束強度,並不是對介面和委託的全方位比較,這裡是希望大家能體會兩種不同的OO多態哲學:介面的哲學是你繼承什麼你就是什麼,委託的哲學是你能做什麼你就是什麼。這兩種哲學反映在C#中就表現出介面的繼承式約束的嚴謹但不靈活的靜態特性,委託的靈活但不那麼嚴謹的動態特性。這一點體悟對理解Duck Typing有很大的協助。
後續
後續章節打算陸續介紹DuckTyping、泛型委派妙用、運算式樹狀架構和lambda、C#4.0動態編程等相關內容,敬請關注,多謝批評!
修改曆史:
1. 2009-03-11 初版
2. 2009-03-15 標題修改為“C#動靜結合編程”;
根據讀者點評,補充說明“動態”和“靜態”的具體指什麼;
補充對介面和委託的說明