近幾年來在TIOBE公司每個月發布的編程語言排行榜[]中總是能擠進前名而在近年的編程語言排行榜中C#總體上呈現上升的趨勢C#能取得這樣的成績有很多因素在起作用其中它在語言特性上的銳意進取讓人印象深刻(圖 )
圖 C#各版本的創新點
年發布的C# 最大的創新點是擁有了動態編程語言的特性
動態編程語言的中興
動態編程語言並非什麼新鮮事物早在面向對象編程語言成為主流之前人們就已經使用動態編程語言來開發了即使在C#等面向對象編程語言繁榮興旺大行於世的年代動態編程語言也在悄悄地攻城掠地占據了相當的開發領域比如 業已成為客戶端事實上的主流語言
最近這幾年動態編程語言變得日益流行比如PythonRuby都非常活躍使用者眾多
這裡有一個問題為什麼我們需要在開發中應用動態編程語言?與C#和Java這類已經非常成熟且功能強大的靜態類型編程語言相比動態編程語言有何優勢?
簡單地說使用動態編程語言開發擁有以下的特性
()支持REPL(Readevaluateprint Loop讀入à執行à輸出循環迭代)的開發模式整個過程簡潔明了直指問題的核心
舉個簡單的例子圖 所示為使用IronPython[]編程計算++……+的屏幕截圖我們可以快速地輸入一段完成累加求和的代碼然後馬上就可以看到結果
圖 使用IronPython編程
如果使用開發就麻煩多了您得先用Visual Studio創建一個項目然後向其中添加一個類在類中寫一個方法完成求和的功能再編寫調用這一方法的代碼編譯排錯最後才能得到所需的結果……
很明顯對於那些短小的工作任務而言動態編程語言所具備的這種REPL開發模式具有很大的吸引力
()擴展方便用戶可以隨時對代碼進行調整需要什麼功能直接往動態對象上加就是了不要時又可以移除它們而且這種修改可以馬上生效並不需要像C#那樣必須先修改類型的定義和聲明編譯之後新方法才可用
換句話說使用動態語言編程不需要重量級的OOAD整個開發過程迭代迅速而從不拖泥帶水
()動態編程語言的類型解析是在運行時完成的可以省去許多不必要的類型轉換代碼因此與靜態編程語相比動態編程語言寫的代碼往往更緊湊量更少
動態編程語言主要的弱點有兩個
()代碼中的許多錯誤要等到運行時才能發現而且需要特定的運行環境支持對其進行測試不太方便也不支持許多用於提升代碼質量的各種工具因此不太適合於開發規模較大的包容復雜處理邏輯的應用系統
()與靜態編程語言相比動態編程語言編寫的程序性能較低不過隨著計算機軟技術的不斷進步比如多核的廣泛應用動態編程語言引擎和運行環境不斷地優化動態編程語言編寫的程序性能在不斷地提升在特定的應用場景下甚至可以逼近靜態語言編寫的程序
擁抱動態編程特性的
為了讓C#Visual Basic等編程語言能具備動態編程語言的特性NET 引入了一個DLR(Dynamic Language Runtime動態語言運行時)(圖 )
圖 DLR動態語言運行時
DLR運行於CLR之上提供了一個動態語言的運行環境從而允許PythonRuby等動態語言編寫的程序在NET平台上運行同時現有的NET靜態類型編程語言比如C#和Visual Basic也可以利用DLR而擁有一些動態編程語言的特性
()使用C# 編寫動態的代碼
C# 新增了一個dynamic關鍵字可以用它來編寫動態的代碼
例如以下代碼創建了一個ExpandoObject對象(注意必須定義為dynamic)
dynamic dynamicObj = new ExpandoObject();
這一對象的奇特之處在於我們可以隨時給它增加新成員
dynamicObjValue = ; //添加字段
dynamicObjIncrement = new Action(() => dynamicObjValue++); //添加方法
這些動態添加的成員與普通的類成員用法一樣
for (int i = ; i < ; i++)
dynamicObjIncrement();//調用方法
ConsoleWriteLine(dynamicObjValue={}dynamicObjValue);//訪問字段
ExpandoObject對象實現了IDictionary<string object>接口可看成是一個字典對象所有動態添加的成員都是這個字典對象中的元素這意味我們不僅可以添加新成員還可以隨時移除不再需要的成員
//移除Increment方法
(dynamicObj as IDictionary<string object>)Remove(Increment);
方法移除之後再嘗試訪問此方法將引發RuntimeBinderException異常
()使用dynamic關鍵字簡化與COM組件交互的代碼
要在這個托管世界裡調用非托管世界中的COM組件我們必須通過 互操作程序集(Interop Assembly)作為橋梁互操作程序集定義了CLR類型與COM類型之間的對應關系
只要給NET項目添加對互操作程序集的引用就可以在NET應用程序中創建這一程序集所包容的各種類型的實例(即COM包裝器對象)對這些對象的方法調用(或對其屬性的存取)將會被轉發給COM組件
以調用Word為例在 之前您可能經常需要編寫這樣的代碼
Object wordapp = new WordApplication(); //創建Word對象
Object fileName = MyDocdocx ;//指定Word文檔
Object argu = SystemReflectionMissingValue;
WordDocument doc = wordappDocumentsOpen(ref fileName ref argu
ref argu ref argu ref argu ref argu ref argu ref argu
ref argu ref argu ref argu ref argu ref argu ref argu
ref argu ref argu);
上述對Open()方法的調用語句只能用恐怖一詞來形容其原因是Word組件中的Open()方法定義了太多的參數
使用dynamic關鍵字配合從Visual Basic中學來的命名參數與可選參數這兩個新語法特性可以寫出更簡潔的代碼
dynamic wordapp = new WordApplication();
dynamic doc = wordappDocumentsOpen(FileName: MyDocdocx);
上述代碼中省去了用不著的參數並且可以去掉參數前的ref關鍵字
當上述代碼運行時DLR會使用反射技術將dynamic表達式綁定(bind)到COM互操作程序集中所包容的WordApplication代理對象
()C# 動態編程技術內幕
C#中所定義的dynamic變量可以引用以下類型的對象
l 傳統的靜態的CLR對象
l COM包裝器對象前面已經介紹了這方面的內容
l 實現了IDynamicMetaObjectProvider接口的動態對象ExpandoObject就是這種類型對象的實例
l 基於DLR實現的動態語言(比如IronRuby和IronPython)所創建的對象
從程序員角度來看所有這四種對象都是一樣的都可用一個dynamic變量引用之而DLR在程序運行時動態地將方法調用和字段存取請求綁定到真正的對象上
dynamic的功能是由DLR所支撐的是C#編譯器與DLR分工合作的成果
請看以下示例代碼
dynamic d = ;
d++;
C#編譯器在處理上述代碼時它並不去檢查變量d是否可以支持自增操作而是為其創建了一個CallSite<T>對象(<>p__Site)
private static class <Main>o__SiteContainer {
public static CallSite<Func<CallSite object object>> <>p__Site;
}
中文MSDN將CallSite<T>譯為動態(調用)站點它是DLR中的核心組件之一
動態站點對象通過CallSite<T>Create()方法創建 C#編譯器會為其指定一個派生自CallSiteBinder的對象(稱為動態站點綁定對象)作為其參數
動態站點綁定對象是與具體語言相關的比如IronPython和C#都有各自的動態站點綁定對象
動態站點綁定對象的主要工作是將代碼中的動態表達式(本例中為d++)轉換為一棵抽象語法樹(ASTAbstract Syntax Tree)這棵語法樹被稱為DLR Tree是在 所引入的LINQ表達式樹的基礎上擴充而來的因此有時又稱其為表達式樹(Expression Tree)
DLR在內部調用此表達式樹的Compile()方法生成IL指令得到一個可以被CLR所執行的委托(在本例中其類型就是Func<CallSite object object>)
動態調用站點對象(本例中為<>p__Site)有一個Target屬性它負責引用這一生成好的委托
委托生成之後動態表達式的執行就體現為委托的執行其實參由編譯器直接寫死在IL代碼中
簡化的代碼示意如下(通過Reflector得到為便於閱讀修改了變量名)
object d = ;
object CS$$ = d;
if (<>p__Site == null)
<>p__Site = CallSite<Func<CallSite object object>>Create(……);
d = <>p__SiteTarget(<>p__Site CS$$);
上述類型推斷方法綁定及IL代碼生成的工作都是在程序運行時完成的
()動態代碼很慢嗎?
動態編程語言易學易用代碼緊湊開發靈活但性能則一直是它的軟肋為了提升性能DLR設計了一個三級緩存策略
動態站點綁定對象會為動態調用表達式轉換而成的語法樹加上相應的測試條件(稱為test)構成一個規則(Rule)這個規則可以用於判斷某個語法樹是否可用於特定的動態調用表達式
舉個例子請看以下這個動態表達式
d + d
如果在程序運行時d和d都是int類型的整數則DLR生成的規則為
if( d is int && d is int) //測試條件
return (int)d+(int)d; //語法樹
DLR通過檢查規則中的測試條件就可以知道某個動態表達式是否可以使用此規則所包容的語法樹
規則是DLR緩存的主要對象
前面介紹過的動態站點對象Target屬性所引用的委托是第一級緩存它實現的處理邏輯是這樣的
//當前處理規則屬於第級緩存
if( d is int && d is int) //測試條件
return (int)d+(int)d; //滿足測試條件直接返回一個表達式樹
//未命中則在第級第級緩存中查找如果找到了用找到的結果更新第級緩存
return siteUpdate(sitedd);
如果級緩存中都沒有命中的規則則此動態站點所關聯的調用站點綁定對象會嘗試創建一個新的規則如果創建新規則失敗則由當前編程語言(比如)所提供的默認調用站點綁定對象決定如何處理通常的作法是拋出一個異常
當前版本的DLR第級緩存了條規則第級則緩存了條規則
由於DLR自身設計了一個規則緩存系統又充分利用了CLR所提供的JIT緩存(因為所有動態調用代碼最終都會轉換為CLR可以執行的IL指令而CLR可以緩存這些代碼)使得動態代碼僅僅在第一次執行時性能較差後續的連續調用其性能可以逼近靜態代碼
C# 與動態語言的集成
由於幾乎所有的編程語言都可以使用抽象語法樹來表達因此在理論上DLR支持無限多種編程語言間的互操作在當前版本中可以實現C#/Visual Basic與IronPython和IronRuby的互操作相信很快會出現其他動態編程語言的DLR實現
一個有趣的地方是當前基於DLR實現的動態編程語言都以Iron開頭比如IronRuby和IronPythonIronPython的設計者DLR的架構設計師Jim Hugunin曾經在微軟PDC 大會上解釋說主要是為了避免起一個Python或Python for NET之類微軟味十足的名字才有了IronPython他強調Iron系列動態語言將嚴格遵循動態語言自身的標准和規范尊重這些動態語言已有的歷史和積累不會引入一些僅限於NET平台的新語言特性並且這些語言的NET實現保持開源與此同時Jim Hugunin指出 Iron系列語言能很好地與NET現有類庫編程語言和工具集成並且能嵌入到NET宿主程序中
()動態對象通訊協議
由於各種動態編程語言之間的特性相差極大實現各語言間的互操作是個難題為此DLR采取了一個聰明的策略它不去嘗試設計一個通用的類型系統(CLR就是這麼干的)而是設計了一個通用的對象通訊協議規定所有需要互操作的動態對象必須實現IDynamicMetaObjectProvider接口此接口定義了一個GetMetaObject()方法接收一個語法樹對象作為參數向外界返回一個動態元數據(DynamicMetaObject)對象
DynamicMetaObject GetMetaObject(Expression parameter);
DynamicMetaObject對象向外界提供了兩個重要屬性Restrictions引用一組測試條件Expression屬性則引用一個語法樹這兩個屬性組合起來就是可供動態站點對象緩存的規則(Rule)
DLR中的動態站點綁定對象(CallSiteBinder)獲取了DynamicMetaObject對象之後它調用此對象所提供的各個方法創建規則讓動態站點對象(CallSite<T>)的Target屬性引用它完成動態綁定的工作
()動態語言集成環境
為了方便地實現靜態編程語言與各種動態編程語言間的相互集成DLR提供了一整套稱為通用寄宿(Common Hosting)的組件其中包容ScriptRuntimeScriptScope等類型
下面我們以IronPython為例介紹如何在 開發的程序中集成動態編程語言代碼
首先需要創建一個ScriptRuntime對象它是一個最頂層的對象用於在一個應用程序域中嵌入一個特定動態語言的運行環境
ScriptRuntime pythonRuntime = PythonCreateRuntime();
接著需要創建一個ScriptEngine對象它是動態語言代碼的執行引擎
ScriptEngine engine = pythonRuntimeGetEngine(py);
ScriptScope對象類似於中的命名空間其中可以通過定義一些變量向動態代碼傳入數據比如下述代碼將一個C# 創建的ExpandoObject對象傳給Python代碼
ScriptScope scope = pythonRuntimeCreateScope();
//C#創建動態對象
dynamic expando = new ExpandoObject();
expandoName = JinXuLiang; //動態添加一個字段
//讓IronPython接收C#創建的Expando對象
scopeSetVariable(ExpandoObject expando);
string pythonCode = print ExpandoObjectName;
//IronPython引擎執行Python語句
engineCreateScriptSourceFromString(pythonCode)Execute(scope);
上述示例代碼是直接執行Python代碼在實際開發中更常見的是直接執行Python文件中的代碼假設有一個Calculatorpy文件其中定義了一個Add函數
def Add(ab):
return a+b
則以下C#代碼可以直接執行之
ScriptRuntime pythonRuntime = PythonCreateRuntime();
dynamic pythonFile = pythonRuntimeUseFile(Calculatorpy);
ConsoleWriteLine(pythonFileAdd( ));
上述示例說明在DLR的支持之下可以讓靜態編程語言使用動態語言所開發的庫反過來基於DLR實現的動態編程語言也能使用為靜態語言所設計的庫比如標准的基類庫
這意味著兩點
()我們現在可以將靜態和動態編程語言組合起來開發出一些具有高度交互性的應用程序使用靜態編程語言搭建系統框架使用動態編程語言實現交互性這是一個很值得注意的應用領域
()將來會出現一些靜態動態編程語言同時適用的庫向實現無所不在的復用目標又前進了一步
Visual Studio 為新的NET編程語言F#提供了專門的項目模板但沒有為IronPython和IronRuby之類動態語言的開發提供支持相信隨著動態語言在NET平台之上的應用日趨廣泛後繼版本的Visual Studio會直接支持動態語言的開發
從C# ~所走過的路可以很清晰地看到它的發展軌跡得到這樣的一個結論
未來的編程語言應該是多范式的具有高度的可組合性在一個項目或產品中組合多個編程語言使用多種編程范式會變得越來越普遍
我們可以推斷C#的後繼版本將會在此條道路上越走越遠……
From:http://tw.wingwit.com/Article/program/ASP/201311/21813.html