當前位置:成語大全網 - 新華字典 - C# 動態編程新特性與DLR剖析

C# 動態編程新特性與DLR剖析

 近幾年來 在TIOBE公司每個月發布的編程語言排行榜[ ]中 總是能擠進前 名 而在近 年的編程語言排行榜中 C#總體上呈現上升的趨勢 C#能取得這樣的成績 有很多因素在起作用 其中 它在語言特性上的銳意進取讓人印象深刻(圖 )

 

 圖 C#各版本的創新點

  年發布的C# 最大的創新點是擁有了動態編程語言的特性

  動態編程語言的中興

 動態編程語言並非什麽新鮮事物 早在面向對象編程語言成為主流之前 人們就已經使用動態編程語言來開發了 即使在 C# 等面向對象編程語言繁榮興旺 大行於世的年代 動態編程語言也在 悄悄 地攻城掠地 占據了相當的開發領域 比如 業已成為客戶端事實上的主流語言

 最近這幾年 動態編程語言變得日益流行 比如Python Ruby都非常活躍 使用者眾多

 這裏有壹個問題 為什麽我們需要在開發中應用動態編程語言?與C#和Java這類已經非常成熟且功能強大的靜態類型編程語言相比 動態編程語言有何優勢?

 簡單地說 使用動態編程語言開發擁有以下的特性

?

 ( )支持REPL(Read evaluate print Loop 讀入à執行à輸出 循環叠代)的開發模式 整個過程簡潔明了 直指問題的核心

 舉個簡單的例子 圖 所示為使用IronPython[ ]編程計算 + +……+ 的屏幕截圖 我們可以快速地輸入壹段完成累加求和的代碼 然後馬上就可以看到結果

 

 圖 使用IronPython編程

 如果使用開發就麻煩多了 您得先用Visual Studio創建壹個項目 然後向其中添加壹個類 在類中寫壹個方法完成求和的功能 再編寫調用這壹方法的代碼 編譯 排錯 最後才能得到所需的結果……

 很明顯 對於那些短小的工作任務而言 動態編程語言所具備的這種REPL開發模式具有很大的吸引力

 ( )擴展方便 用戶可以隨時對代碼進行調整 需要什麽功能直接往動態對象上 加 就是了 不要時又可以移除它們 而且這種修改可以馬上生效 並不需要像C#那樣必須先修改類型的定義和聲明 編譯之後新方法才可用

 換句話說 使用動態語言編程 不需要 重量級 的OOAD 整個開發過程叠代迅速而從不拖泥帶水

 ( )動態編程語言的類型解析是在運行時完成的 可以省去許多不必要的類型轉換代碼 因此 與靜態編程語相比 動態編程語言寫的代碼往往更緊湊 量更少

 動態編程語言主要的弱點有兩個

?

 ( )代碼中的許多錯誤要等到運行時才能發現 而且需要特定的運行環境支持 對其進行測試不太方便 也不支持許多用於提升代碼質量的各種工具 因此不太適合於開發規模較大的 包容復雜處理邏輯的應用系統

 ( )與靜態編程語言相比 動態編程語言編寫的程序性能較低 不過隨著計算機軟技術的不斷進步 比如多核的廣泛應用 動態編程語言引擎和運行環境不斷地優化 動態編程語言編寫的程序性能在不斷地提升 在特定的應用場景下 甚至可以逼近靜態語言編寫的程序

  擁抱 動態編程 特性的

 為了讓C# Visual Basic等編程語言能具備動態編程語言的特性 NET 引入了壹個 DLR(Dynamic Language Runtime 動態語言運行時) (圖 )

 

 圖 DLR 動態語言運行時

 DLR運行於CLR之上 提供了壹個動態語言的運行環境 從而允許Python Ruby等動態語言編寫的程序在 NET平臺上運行 同時 現有的 NET靜態類型編程語言 比如C#和Visual Basic 也可以利用DLR而擁有壹些動態編程語言的特性

 ( )使用C# 編寫動態的代碼

 C# 新增了壹個dynamic關鍵字 可以用它來編寫 動態 的代碼

 例如 以下代碼創建了壹個ExpandoObject對象(註意必須定義為dynamic)

?

 dynamic dynamicObj = new ExpandoObject();

 這壹對象的奇特之處在於 我們可以隨時給它增加新成員

dynamicObj Value =  ; //添加字段    dynamicObj Increment = new Action(() => dynamicObj Value++); //添加方法

 這些動態添加的成員與普通的類成員用法壹樣

for (int i =  ; i <  ; i++)        dynamicObj Increment();//調用方法    Console WriteLine( dynamicObj Value={ } dynamicObj Value);//訪問字段

 ExpandoObject對象實現了IDictionary<string object>接口 可看成是壹個字典對象 所有動態添加的成員都是這個字典對象中的元素 這意味我們不僅可以添加新成員 還可以隨時移除不再需要的成員

//移除Increment方法    (dynamicObj as IDictionary<stringobject>) Remove( Increment );

 方法移除之後 再嘗試訪問此方法將引發RuntimeBinderException異常

 ( )使用dynamic關鍵字簡化與組件交互的代碼

 要在這個 托管世界 裏調用 非托管世界 中的組件 我們必須通過 互操作程序集(Interop Assembly) 作為橋梁 互操作程序集 定義了CLR類型與類型之間的對應關系

 只要給 NET項目添加對 互操作程序集 的引用 就可以在 NET應用程序中創建這壹程序集所包容的各種類型的實例(即包裝器對象) 對這些對象的方法調用(或對其屬性的存取)將會被轉發給組件

 以調用Word為例 在 之前您可能經常需要編寫這樣的代碼

?

Object wordapp = new Word Application();   //創建Word對象    Object fileName =  MyDoc docx;//指定Word文檔    Object argu = System Reflection Missing Value;    Word Document doc = wordapp Documents Open(ref fileNameref argu                ref arguref arguref arguref arguref arguref argu               ref arguref arguref arguref arguref arguref argu                ref arguref argu);

 上述對Open()方法的調用語句只能用 恐怖 壹詞來形容 其原因是Word組件中的Open()方法定義了太多的參數

  使用dynamic關鍵字 配合從Visual Basic中學來的 命名參數與可選參數 這兩個新語法特性 可以寫出更簡潔的代碼

dynamic wordapp = new Word Application();    dynamic doc = wordapp Documents Open(FileName:  MyDoc docx );

 上述代碼中省去了用不著的參數 並且可以去掉參數前的ref關鍵字

 當上述代碼運行時 DLR會使用反射技術將dynamic表達式 綁定(bind) 到互操作程序集中所包容的Word Application代理對象

 ( )C# 動態編程技術內幕

 C# 中所定義的dynamic變量可以引用以下類型的對象

 l 傳統的 靜態 的CLR對象

 l 包裝器對象 前面已經介紹了這方面的內容

 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<CallSiteobjectobject>> <>p__Site ;    }

 中文MSDN將CallSite<T>譯為 動態(調用)站點 它是DLR中的核心組件之壹

 動態站點對象通過CallSite<T> Create()方法創建 C#編譯器會為其指定壹個派生自CallSiteBinder的對象(稱為 動態站點綁定對象 )作為其參數

 動態站點綁定對象是與具體語言相關的 比如IronPython和C#都有各自的動態站點綁定對象

 動態站點綁定對象的主要工作是將代碼中的動態表達式(本例中為d++)轉換為壹棵 抽象語法樹(AST Abstract 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<CallSiteobjectobject>> Create(……);    d = <>p__Site Target(<>p__SiteCS$ $ );

 上述類型推斷 方法綁定及IL代碼生成的工作都是在程序運行時完成的

 ( )動態代碼很慢嗎?

 動態編程語言易學易用 代碼緊湊 開發靈活 但性能則壹直是它的 軟肋 為了提升性能 DLR設計了壹個三級緩存策略

 動態站點綁定對象會為動態調用表達式轉換而成的語法樹加上相應的測試條件(稱為 test ) 構成壹個 規則(Rule) 這個規則可以用於判斷某個語法樹是否可用於特定的動態調用表達式

 舉個例子 請看以下這個動態表達式

 d + d

 如果在程序運行時d 和d 都是int類型的整數 則DLR生成的規則為

if( dis int && dis int) //測試條件        return (int)d +(int)d ; //語法樹

 DLR通過檢查規則中的 測試條件 就可以知道某個動態表達式是否可以使用此規則所包容的語法樹

  規則 是DLR緩存的主要對象

?

 前面介紹過的動態站點對象Target屬性所引用的委托是第壹級緩存 它實現的處理邏輯是這樣的

//當前處理規則 屬於第 級緩存    if( dis int && dis int) //測試條件        return (int)d +(int)d ; //滿足測試條件 直接返回壹個表達式樹    //未命中 則在第 級 第 級緩存中查找 如果找到了 用找到的結果更新第 級緩存    return site Update(site d d ); 

 如果 級緩存中都沒有命中的規則 則此動態站點所關聯的調用站點綁定對象會嘗試創建壹個新的規則 如果創建新規則失敗 則由當前編程語言(比如)所提供的默認調用站點綁定對象決定如何處理 通常的作法是拋出壹個異常

 當前版本的DLR第 級緩存了 條規則 第 級則緩存了 條規則

 由於DLR自身設計了壹個 規則 緩存系統 又充分利用了CLR所提供的JIT緩存(因為所有動態調用代碼最終都會轉換為CLR可以執行的IL指令 而CLR可以緩存這些代碼) 使得動態代碼僅僅在第壹次執行時性能較差 後續的連續調用其性能可以逼近靜態代碼

  C# 與動態語言的集成

 由於幾乎所有的編程語言都可以使用抽象語法樹來表達 因此 在理論上DLR支持無限多種編程語言間的互操作 在當前版本中 可以實現C#/Visual Basic與IronPython和IronRuby的互操作 相信很快會出現其他動態編程語言的DLR實現

 壹個有趣的地方是當前基於DLR實現的動態編程語言都以 Iron 開頭 比如IronRuby和IronPython IronPython的設計者 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) 的組件 其中包容ScriptRuntime ScriptScope等類型

 下面我們以IronPython為例 介紹如何在 開發的程序中集成動態編程語言代碼

 首先需要創建壹個ScriptRuntime對象 它是壹個最頂層的對象 用於在壹個應用程序域中 嵌入 壹個特定動態語言的運行環境

 ScriptRuntime pythonRuntime = Python CreateRuntime();

?

 接著需要創建壹個ScriptEngine對象 它是動態語言代碼的執行引擎

 ScriptEngine engine = pythonRuntime GetEngine( py );

 ScriptScope對象類似於中的命名空間 其中可以通過定義壹些變量向動態代碼傳入數據 比如下述代碼將壹個C# 創建的ExpandoObject對象傳給Python代碼

ScriptScope scope = pythonRuntime CreateScope();    //C#創建動態對象     dynamic expando = new ExpandoObject();    expando Name =  JinXuLiang ; //動態添加壹個字段     //讓IronPython接收C#創建的Expando對象    scope SetVariable( ExpandoObjectexpando);    string pythonCode =  print ExpandoObject Name ;     //IronPython引擎執行Python語句    engine CreateScriptSourceFromString(pythonCode) Execute(scope);      

 上述示例代碼是直接執行Python代碼 在實際開發中 更常見的是直接執行Python文件中的代碼 假設有壹個Calculator py文件 其中定義了壹個Add函數

def Add(a b):        return a+b

 則以下C#代碼可以直接執行之

ScriptRuntime pythonRuntime = Python CreateRuntime();    dynamic pythonFile = pythonRuntime UseFile( Calculator py );    Console WriteLine(pythonFile Add( ));

 上述示例說明在DLR的支持之下 可以讓靜態編程語言使用動態語言所開發的庫 反過來 基於DLR實現的動態編程語言也能使用為靜態語言所設計的庫 比如標準的基類庫

 這意味著兩點

 ( )我們現在可以將 靜態 和 動態 編程語言組合起來 開發出壹些具有高度交互性的應用程序 使用靜態編程語言搭建系統框架 使用動態編程語言實現交互性 這是壹個很值得註意的應用領域

 ( )將來會出現壹些 靜態 動態 編程語言同時適用的庫 向實現 無所不在的復用 目標又前進了壹步

 Visual Studio 為新的 NET編程語言F#提供了專門的項目模板 但沒有為IronPython和IronRuby之類動態語言的開發提供支持 相信隨著動態語言在 NET平臺之上的應用日趨廣泛 後繼版本的Visual Studio會直接支持動態語言的開發

 從C# ~ 所走過的路 可以很清晰地看到它的發展軌跡 得到這樣的壹個結論

 未來的編程語言應該是多範式的 具有高度的可組合性 在壹個項目或產品中組合多個編程語言 使用多種編程範式會變得越來越普遍

lishixinzhi/Article/program/ASP/201311/21813