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

===================================================php

注:
好久沒有發文了,貼一篇新文吧。從Word直接貼過來的,沒仔細排版,諸位海涵。
有關DLR和C# 4動態特性的詳細介紹,請參看本人拙著《.NET 4.0面向對象編程揭祕(應用篇)》,目前該書正處於編輯出版流程中,估計12月上市。
與此書相關的技術資源,將陸續發佈於博客園與CSDN的本人博客。
html

 =====================================================================================python

金旭亮jquery

 

 

 

         近幾年來,在TIOBE公司每月發佈的編程語言排行榜[1]中,C#老是能擠進前10名,而在近10年的編程語言排行榜中,C#整體上呈現上升的趨勢。C#能取得這樣的成績,有不少因素在起做用,其中,它在語言特性上的銳意進取讓人印象深入( 1)。程序員

 

 

1 C#各版本的創新點編程

 

         2010年發佈的C# 4,最大的創新點是擁有了動態編程語言的特性。緩存

動態編程語言的中興

         動態編程語言並不是什麼新鮮事物,早在面向對象編程語言成爲主流以前,人們就已經使用動態編程語言來開發了。即便在JavaC#C++等面向對象編程語言繁榮興旺、大行於世的年代,動態編程語言也在「悄悄」地攻城掠地,佔據了至關的開發領域,好比 JavaScript業已成爲Web客戶端事實上的主流語言。架構

         最近這幾年,動態編程語言變得日益流行,好比PythonRuby都很是活躍,使用者衆多。app

         這裏有一個問題,爲何咱們須要在開發中應用動態編程語言?與C#Java這類已經很是成熟且功能強大的靜態類型編程語言相比,動態編程語言有何優點?框架

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

         1)支持REPLRead-evaluate-print Loop:「讀入à執行à輸出」循環迭代)的開發模式,整個過程簡潔明瞭,直指問題的核心。

         舉個簡單的例子, 2所示爲使用IronPython[2]編程計算「1+2+……+100」的屏幕截圖,咱們能夠快速地輸入一段完成累加求和的代碼,而後立刻就能夠看到結果:

 

 

2 使用IronPython編程

 

         若是使用C#開發就麻煩多了,您得先用Visual Studio建立一個項目,而後向其中添加一個類,在類中寫一個方法完成求和的功能,再編寫調用這一方法的代碼,編譯、排錯,最後才能獲得所需的結果……

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

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

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

         3)動態編程語言的類型解析是在運行時完成的,能夠省去許多沒必要要的類型轉換代碼,所以,與靜態編程語相比,動態編程語言寫的代碼每每更緊湊,量更少。

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

         1)代碼中的許多錯誤要等到運行時才能發現,並且須要特定的運行環境支持,對其進行測試不太方便,也不支持許多用於提高代碼質量的各類軟件工程工具,所以不太適合於開發規模較大的、包容複雜處理邏輯的應用系統。

         2)與靜態編程語言相比,動態編程語言編寫的程序性能較低。不過隨着計算機軟硬件技術的不斷進步,好比多核CPU的普遍應用,動態編程語言引擎和運行環境不斷地優化,動態編程語言編寫的程序性能在不斷地提高,在特定的應用場景下,甚至能夠逼近靜態語言編寫的程序。

擁抱「動態編程」特性的C# 4

         爲了讓C#Visual Basic.NET編程語言能具有動態編程語言的特性,.NET 4.0引入了一個「DLRDynamic Language Runtime:動態語言運行時)」( 3)。

 

 

3 DLR:動態語言運行時

 

         DLR運行於CLR之上,提供了一個動態語言的運行環境,從而容許PythonRuby等動態語言編寫的程序在.NET平臺上運行,同時,現有的.NET靜態類型編程語言,好比C#Visual Basic,也能夠利用DLR而擁有一些動態編程語言的特性。

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

         C# 4新增了一個dynamic關鍵字,能夠用它來編寫「動態」的代碼。

         例如,如下代碼建立了一個ExpandoObject對象(注意必須定義爲dynamic):

 

      dynamic dynamicObj = new ExpandoObject();

 

         這一對象的奇特之處在於,咱們能夠隨時給它增長新成員:

        

    dynamicObj.Value = 100; //添加字段

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

 

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

 

    for (int i = 0; i < 10; i++)

        dynamicObj.Increment();//調用方法

    Console.WriteLine("dynamicObj.Value={0}",dynamicObj.Value);//訪問字段

 

         ExpandoObject對象實現了IDictionary<string, object>接口,可當作是一個字典對象,全部動態添加的成員都是這個字典對象中的元素,這意味咱們不只能夠添加新成員,還能夠隨時移除再也不須要的成員:

 

    //移除Increment方法

    (dynamicObj as IDictionary<string, object>).Remove("Increment");

 

         方法移除以後,再嘗試訪問此方法將引起RuntimeBinderException異常。

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

    要在.NET這個「託管世界」裏調用「非託管世界」中的COM組件,咱們必須經過 「互操做程序集(Interop Assembly)」做爲橋樑,「互操做程序集」定義了CLR類型與COM類型之間的對應關係。

         只要給.NET項目添加對「互操做程序集」的引用,就能夠在.NET應用程序中建立這一程序集所包容的各類類型的實例(即COM包裝器對象),對這些對象的方法調用(或對其屬性的存取)將會被轉發給COM組件。

         以調用Word爲例,在C# 4.0以前您可能常常須要編寫這樣的代碼:

 

    Object wordapp = new Word.Application();   //建立Word對象

    Object fileName = 「MyDoc.docx」 ;//指定Word文檔

    Object argu = System.Reflection.Missing.Value;

    Word.Document doc = wordapp.Documents.Open(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()方法定義了太多的參數。

         C#4使用dynamic關鍵字,配合從Visual Basic中學來的「命名參數與可選參數」這兩個新語法特性,能夠寫出更簡潔的代碼:

 

    dynamic wordapp = new Word.Application();

    dynamic doc = wordapp.Documents.Open(FileName: 「MyDoc.docx」);

 

         上述代碼中省去了用不着的參數,而且能夠去掉參數前的ref關鍵字。

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

3C# 4動態編程技術內幕

         C#4中所定義的dynamic變量能夠引用如下類型的對象:

傳統的「靜態」的CLR對象。

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

實現了IDynamicMetaObjectProvider接口的「動態對象」,ExpandoObject就是這種類型對象的實例。

基於DLR實現的動態語言(好比IronRubyIronPython)所建立的對象。

         C#程序員角度來看,全部這四種對象都是同樣的,均可用一個dynamic變量引用之,而DLR在程序運行時動態地將方法調用和字段存取請求「綁定」到真正的對象上。

         dynamic的功能是由DLR所支撐的,是C#編譯器與DLR分工合做的成果。

         請看如下示例代碼:

 

    dynamic d = 100;

    d++;

 

         C#編譯器在處理上述代碼時,它並不去檢查變量d是否能夠支持自增操做,而是爲其建立了一個CallSite<T>對象(<>p__Site1):

 

    private static class <Main>o__SiteContainer0 {

        public static CallSite<Func<CallSite, object, object>> <>p__Site1;

    }

 

         中文MSDNCallSite<T>譯爲「動態(調用)站點」,它是DLR中的核心組件之一。

         動態站點對象經過CallSite<T>.Create()方法建立, C#編譯器會爲其指定一個派生自CallSiteBinder的對象(稱爲「動態站點綁定對象」)做爲其參數。

         動態站點綁定對象是與具體語言相關的,好比IronPythonC#都有各自的動態站點綁定對象。

         動態站點綁定對象的主要工做是將代碼中的動態表達式(本例中爲d++)轉換爲一棵「抽象語法樹(ASTAbstract Syntax Tree)」,這棵語法樹被稱爲「DLR Tree」,是在.NET 3.5所引入的LINQ表達式樹的基礎上擴充而來的,所以,有時又稱其爲「表達式樹(Expression Tree)」

         DLR在內部調用此表達式樹的Compile()方法生成IL指令,獲得一個能夠被CLR所執行的委託(在本例中其類型就是Func<CallSite, object, object>)。

         動態調用站點對象(本例中爲<>p__Site1)有一個Target屬性,它負責引用這一輩子成好的委託。

         委託生成以後,動態表達式的執行就體現爲委託的執行,其實參由C#編譯器直接「寫死」在IL代碼中。

         簡化的代碼示意以下(經過Reflector獲得,爲便於閱讀,修改了變量名):

 

    object d = 100;

    object CS$0$0000 = d;

    if (<>p__Site1 == null)

        <>p__Site1 = CallSite<Func<CallSite, object, object>>.Create(……);

    d = <>p__Site1.Target(<>p__Site1, CS$0$0000);

      

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

4)動態代碼很慢嗎?

         動態編程語言易學易用,代碼緊湊,開發靈活,但性能則一直是它的「軟肋」。爲了提高性能,DLR設計了一個三級緩存策略。

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

         舉個例子,請看如下這個動態表達式:

 

    d1 + d2

 

         若是在程序運行時d1d2都是int類型的整數,則DLR生成的規則爲:

      

    if( d1 is int && d2 is int) //測試條件

        return (int)d1+(int)d2; //語法樹

 

         DLR經過檢查規則中的「測試條件」,就能夠知道某個動態表達式是否可使用此規則所包容的語法樹。

         「規則」是DLR緩存的主要對象。

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

 

    //當前處理規則,屬於第1級緩存

    if( d1 is int && d2 is int) //測試條件

        return (int)d1+(int)d2; //知足測試條件,直接返回一個表達式樹

    //未命中,則在第2級、第3級緩存中查找,若是找到了,用找到的結果更新第1級緩存

    return site.Update(site,d1,d2);

 

         若是3級緩存中都沒有命中的規則,則此動態站點所關聯的調用站點綁定對象會嘗試建立一個新的規則。若是建立新規則失敗,則由當前編程語言(好比C#)所提供的默認調用站點綁定對象決定如何處理,一般的做法是拋出一個異常。

         當前版本的DLR2級緩存了10條規則,第3級則緩存了100條規則。

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

3 C# 4與動態語言的集成

         因爲幾乎全部的編程語言均可以使用抽象語法樹來表達,所以,在理論上DLR支持無限多種編程語言間的互操做,在當前版本中,能夠實現C#/Visual BasicIronPythonIronRuby的互操做,相信很快會出現其餘動態編程語言的DLR實現。

         一個有趣的地方是當前基於DLR實現的動態編程語言都以「Iron」開頭,好比IronRubyIronPythonIronPython的設計者、DLR的架構設計師Jim Hugunin曾經在微軟PDC 2008大會上解釋說主要是爲了不起一個「Python.NET」或「Python for .NET」之類「微軟味十足」的名字,纔有了「IronPython」。他強調:「Iron」系列動態語言將嚴格遵循動態語言自身的標準和規範,尊重這些動態語言已有的歷史和積累,不會引入一些僅限於.NET平臺的新語言特性,而且這些語言的.NET實現保持開源。與此同時,Jim Hugunin指出 Iron」系列語言能很好地與.NET現有類庫、編程語言和工具集成,而且能「嵌入」到.NET宿主程序中。

1)動態對象通信協議

         因爲各類動態編程語言之間的特性相差極大,實現各語言間的互操做是個難題。爲此DLR採起了一個聰明的策略,它不去嘗試設計一個「通用的類型系統」(CLR就是這麼幹的),而是設計了一個「通用的對象通信協議」,規定全部須要互操做的動態對象必須實現IDynamicMetaObjectProvider接口,此接口定義了一個GetMetaObject()方法,接收一個語法樹對象做爲參數,向外界返回一個「動態元數據(DynamicMetaObject)」對象:

 

        DynamicMetaObject GetMetaObject(Expression parameter);

 

   DynamicMetaObject對象向外界提供了兩個重要屬性:Restrictions引用一組測試條件,Expression屬性則引用一個語法樹。這兩個屬性組合起來就是可供動態站點對象緩存的「規則(Rule)」。

         DLR中的「動態站點綁定對象(CallSiteBinder)」獲取了DynamicMetaObject對象以後,它調用此對象所提供的各個方法建立「規則」,讓「動態站點對象(CallSite<T>)」的Target屬性引用它,完成動態綁定的工做。

2)動態語言集成環境

         爲了方便地實現靜態編程語言與各類動態編程語言間的相互集成,DLR提供了一整套稱爲「通用寄宿(Common Hosting)」的組件,其中包容ScriptRuntimeScriptScope等類型。

         下面咱們以IronPython爲例,介紹如何在C# 4開發的程序中集成動態編程語言代碼。

         首先須要建立一個ScriptRuntime對象,它是一個最頂層的對象,用於在一個.NET應用程序域中「嵌入」一個特定動態語言的運行環境:

 

    ScriptRuntime pythonRuntime = Python.CreateRuntime();

 

         接着須要建立一個ScriptEngine對象,它是動態語言代碼的執行引擎:

 

    ScriptEngine engine = pythonRuntime.GetEngine("py");

 

         ScriptScope對象相似於C#中的命名空間,其中能夠經過定義一些變量向動態代碼傳入數據,好比下述代碼將一個C# 建立的ExpandoObject對象傳給Python代碼:

 

    ScriptScope scope = pythonRuntime.CreateScope();

    //C#建立動態對象

     dynamic expando = new ExpandoObject();

    expando.Name = "JinXuLiang"; //動態添加一個字段

     //IronPython接收C#建立的Expando對象

    scope.SetVariable("ExpandoObject", expando);

    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(100, 200));

 

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

         這意味着兩點:

         1)咱們如今能夠將「靜態」和「動態」編程語言組合起來,開發出一些具備高度交互性的應用程序,使用靜態編程語言搭建系統框架,使用動態編程語言實現交互性,這是一個很值得注意的應用領域。

         2)未來會出現一些「靜態」「動態」編程語言同時適用的庫,向實現「無所不在的複用」目標又前進了一步。

         Visual Studio 2010爲新的.NET編程語言F#提供了專門的項目模板,但沒有爲IronPythonIronRuby之類動態語言的開發提供支持,相信隨着動態語言在.NET平臺之上的應用日趨普遍,後繼版本的Visual Studio會直接支持動態語言的開發。

         C# 1.0~4.0所走過的路,能夠很清晰地看到它的發展軌跡,獲得這樣的一個結論:

         將來的編程語言應該是多範式的,具備高度的可組合性,在一個項目或產品中組合多個編程語言、使用多種編程範式會變得愈來愈廣泛。

         咱們能夠推斷C#的後繼版本將會在此條道路上越走越遠……

 

 



[1]  http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html

[2]  IronPython是動態語言Python基於.NET的一個實現

相關文章
相關標籤/搜索