C# 4何以成為微軟鋒利的刀——動態(tài)編程語言
近幾年來,在TIOBE公司每個月發(fā)布的編程語言排行榜[1]中,C#總是能擠進(jìn)前10名,而在近10年的編程語言排行榜中,C#總體上呈現(xiàn)上升的趨勢。C#能取得這樣的成績,有很多因素在起作用,其中,它在語言特性上的銳意進(jìn)取讓人印象深刻(圖 1)。51CTO向您推薦《8月編程語言排行榜:微軟鋒利的刀C#》
圖 1 C#各版本的創(chuàng)新點(diǎn)
2010年發(fā)布的C# 4,最大的創(chuàng)新點(diǎn)是擁有了動態(tài)編程語言的特性。
1 動態(tài)編程語言的中興
動態(tài)編程語言并非什么新鮮事物,早在面向?qū)ο缶幊陶Z言成為主流之前,人們就已經(jīng)使用動態(tài)編程語言來開發(fā)了。即使在Java、C#、C++等面向?qū)ο缶幊陶Z言繁榮興旺、大行于世的年代,動態(tài)編程語言也在“悄悄”地攻城掠地,占據(jù)了相當(dāng)?shù)拈_發(fā)領(lǐng)域,比如 JavaScript業(yè)已成為Web客戶端事實(shí)上的主流語言。
最近這幾年,動態(tài)編程語言變得日益流行,比如Python、Ruby都非常活躍,使用者眾多。
這里有一個問題,為什么我們需要在開發(fā)中應(yīng)用動態(tài)編程語言?與C#和Java這類已經(jīng)非常成熟且功能強(qiáng)大的靜態(tài)類型編程語言相比,動態(tài)編程語言有何優(yōu)勢?
簡單地說,使用動態(tài)編程語言開發(fā)擁有以下的特性:
(1)支持REPL(Read-evaluate-print Loop:“讀入à執(zhí)行à輸出”循環(huán)迭代)的開發(fā)模式,整個過程簡潔明了,直指問題的核心。
舉個簡單的例子,圖 2所示為使用IronPython[2]編程計(jì)算“1+2+……+100”的屏幕截圖,我們可以快速地輸入一段完成累加求和的代碼,然后馬上就可以看到結(jié)果:
圖 2 使用IronPython編程
如果使用C#開發(fā)就麻煩多了,您得先用Visual Studio創(chuàng)建一個項(xiàng)目,然后向其中添加一個類,在類中寫一個方法完成求和的功能,再編寫調(diào)用這一方法的代碼,編譯、排錯,最后才能得到所需的結(jié)果……
很明顯,對于那些短小的工作任務(wù)而言,動態(tài)編程語言所具備的這種REPL開發(fā)模式具有很大的吸引力。
(2)擴(kuò)展方便。用戶可以隨時對代碼進(jìn)行調(diào)整,需要什么功能直接往動態(tài)對象上“加”就是了,不要時又可以移除它們。而且這種修改可以馬上生效,并不需要像C#那樣必須先修改類型的定義和聲明,編譯之后新方法才可用。
換句話說:使用動態(tài)語言編程,不需要“重量級”的OOAD,整個開發(fā)過程迭代迅速而從不拖泥帶水。
(3)動態(tài)編程語言的類型解析是在運(yùn)行時完成的,可以省去許多不必要的類型轉(zhuǎn)換代碼,因此,與靜態(tài)編程語相比,動態(tài)編程語言寫的代碼往往更緊湊,量更少。
動態(tài)編程語言主要的弱點(diǎn)有兩個:
(1)代碼中的許多錯誤要等到運(yùn)行時才能發(fā)現(xiàn),而且需要特定的運(yùn)行環(huán)境支持,對其進(jìn)行測試不太方便,也不支持許多用于提升代碼質(zhì)量的各種軟件工程工具,因此不太適合于開發(fā)規(guī)模較大的、包容復(fù)雜處理邏輯的應(yīng)用系統(tǒng)。
(2)與靜態(tài)編程語言相比,動態(tài)編程語言編寫的程序性能較低。不過隨著計(jì)算機(jī)軟硬件技術(shù)的不斷進(jìn)步,比如多核CPU的廣泛應(yīng)用,動態(tài)編程語言引擎和運(yùn)行環(huán)境不斷地優(yōu)化,動態(tài)編程語言編寫的程序性能在不斷地提升,在特定的應(yīng)用場景下,甚至可以逼近靜態(tài)語言編寫的程序。
#p#
2 擁抱“動態(tài)編程”特性的C# 4
為了讓C#、Visual Basic等.NET編程語言能具備動態(tài)編程語言的特性,.NET 4.0引入了一個“DLR(Dynamic Language Runtime:動態(tài)語言運(yùn)行時)”(圖 3)。
圖 3 DLR:動態(tài)語言運(yùn)行時
DLR運(yùn)行于CLR之上,提供了一個動態(tài)語言的運(yùn)行環(huán)境,從而允許Python、Ruby等動態(tài)語言編寫的程序在.NET平臺上運(yùn)行,同時,現(xiàn)有的.NET靜態(tài)類型編程語言,比如C#和Visual Basic,也可以利用DLR而擁有一些動態(tài)編程語言的特性。
(1)使用C# 4編寫動態(tài)的代碼
C# 4新增了一個dynamic關(guān)鍵字,可以用它來編寫“動態(tài)”的代碼。
例如,以下代碼創(chuàng)建了一個ExpandoObject對象(注意必須定義為dynamic):
- dynamic dynamicObj = new ExpandoObject();
這一對象的奇特之處在于,我們可以隨時給它增加新成員:
- dynamicObj.Value = 100; //添加字段
- dynamicObj.Increment = new Action(() => dynamicObj.Value++); //添加方法
這些動態(tài)添加的成員與普通的類成員用法一樣:
- for (int i = 0; i < 10; i++)
- dynamicObj.Increment();//調(diào)用方法
- Console.WriteLine("dynamicObj.Value={0}",dynamicObj.Value);//訪問字段
ExpandoObject對象實(shí)現(xiàn)了IDictionary<string, object>接口,可看成是一個字典對象,所有動態(tài)添加的成員都是這個字典對象中的元素,這意味我們不僅可以添加新成員,還可以隨時移除不再需要的成員:
- //移除Increment方法
- (dynamicObj as IDictionary<string, object>).Remove("Increment");
方法移除之后,再嘗試訪問此方法將引發(fā)RuntimeBinderException異常。
(2)使用dynamic關(guān)鍵字簡化與COM組件交互的代碼
要在.NET這個“托管世界”里調(diào)用“非托管世界”中的COM組件,我們必須通過 “互操作程序集(Interop Assembly)”作為橋梁,“互操作程序集”定義了CLR類型與COM類型之間的對應(yīng)關(guān)系。
只要給.NET項(xiàng)目添加對“互操作程序集”的引用,就可以在.NET應(yīng)用程序中創(chuàng)建這一程序集所包容的各種類型的實(shí)例(即COM包裝器對象),對這些對象的方法調(diào)用(或?qū)ζ鋵傩缘拇嫒。晦D(zhuǎn)發(fā)給COM組件。
以調(diào)用Word為例,在C# 4.0之前您可能經(jīng)常需要編寫這樣的代碼:
- Object wordapp = new Word.Application(); //創(chuàng)建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()方法的調(diào)用語句只能用“恐怖”一詞來形容,其原因是Word組件中的Open()方法定義了太多的參數(shù)。
C#4使用dynamic關(guān)鍵字,配合從Visual Basic中學(xué)來的“命名參數(shù)與可選參數(shù)”這兩個新語法特性,可以寫出更簡潔的代碼:
- dynamic wordapp = new Word.Application();
- dynamic doc = wordapp.Documents.Open(FileName: “MyDoc.docx”);
上述代碼中省去了用不著的參數(shù),并且可以去掉參數(shù)前的ref關(guān)鍵字。
當(dāng)上述代碼運(yùn)行時,DLR會使用反射技術(shù)將dynamic表達(dá)式“綁定(bind)”到COM互操作程序集中所包容的Word.Application代理對象。
(3)C# 4動態(tài)編程技術(shù)內(nèi)幕
C#4中所定義的dynamic變量可以引用以下類型的對象:
l 傳統(tǒng)的“靜態(tài)”的CLR對象。
l COM包裝器對象。前面已經(jīng)介紹了這方面的內(nèi)容。
l 實(shí)現(xiàn)了IDynamicMetaObjectProvider接口的“動態(tài)對象”,ExpandoObject就是這種類型對象的實(shí)例。
l 基于DLR實(shí)現(xiàn)的動態(tài)語言(比如IronRuby和IronPython)所創(chuàng)建的對象。
從C#程序員角度來看,所有這四種對象都是一樣的,都可用一個dynamic變量引用之,而DLR在程序運(yùn)行時動態(tài)地將方法調(diào)用和字段存取請求“綁定”到真正的對象上。
dynamic的功能是由DLR所支撐的,是C#編譯器與DLR分工合作的成果。
請看以下示例代碼:
- dynamic d = 100;
- d++;
C#編譯器在處理上述代碼時,它并不去檢查變量d是否可以支持自增操作,而是為其創(chuàng)建了一個CallSite<T>對象(<>p__Site1):
- private static class <Main>o__SiteContainer0 {
- public static CallSite<Func<CallSite, object, object>> <>p__Site1;
- }
中文MSDN將CallSite<T>譯為“動態(tài)(調(diào)用)站點(diǎn)”,它是DLR中的核心組件之一。
動態(tài)站點(diǎn)對象通過CallSite<T>.Create()方法創(chuàng)建, C#編譯器會為其指定一個派生自CallSiteBinder的對象(稱為“動態(tài)站點(diǎn)綁定對象”)作為其參數(shù)。
動態(tài)站點(diǎn)綁定對象是與具體語言相關(guān)的,比如IronPython和C#都有各自的動態(tài)站點(diǎn)綁定對象。
動態(tài)站點(diǎn)綁定對象的主要工作是將代碼中的動態(tài)表達(dá)式(本例中為d++)轉(zhuǎn)換為一棵“抽象語法樹(AST:Abstract Syntax Tree)”,這棵語法樹被稱為“DLR Tree”,是在.NET 3.5所引入的LINQ表達(dá)式樹的基礎(chǔ)上擴(kuò)充而來的,因此,有時又稱其為“表達(dá)式樹(Expression Tree)”
DLR在內(nèi)部調(diào)用此表達(dá)式樹的Compile()方法生成IL指令,得到一個可以被CLR所執(zhí)行的委托(在本例中其類型就是Func<CallSite, object, object>)。
動態(tài)調(diào)用站點(diǎn)對象(本例中為<>p__Site1)有一個Target屬性,它負(fù)責(zé)引用這一生成好的委托。
委托生成之后,動態(tài)表達(dá)式的執(zhí)行就體現(xiàn)為委托的執(zhí)行,其實(shí)參由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代碼生成的工作都是在程序運(yùn)行時完成的。
(4)動態(tài)代碼很慢嗎?
動態(tài)編程語言易學(xué)易用,代碼緊湊,開發(fā)靈活,但性能則一直是它的“軟肋”。為了提升性能,DLR設(shè)計(jì)了一個三級緩存策略。
動態(tài)站點(diǎn)綁定對象會為動態(tài)調(diào)用表達(dá)式轉(zhuǎn)換而成的語法樹加上相應(yīng)的測試條件(稱為“test”),構(gòu)成一個“規(guī)則(Rule)”,這個規(guī)則可以用于判斷某個語法樹是否可用于特定的動態(tài)調(diào)用表達(dá)式。
舉個例子,請看以下這個動態(tài)表達(dá)式:
d1 + d2
如果在程序運(yùn)行時d1和d2都是int類型的整數(shù),則DLR生成的規(guī)則為:
- if( d1 is int && d2 is int) //測試條件
- return (int)d1+(int)d2; //語法樹
DLR通過檢查規(guī)則中的“測試條件”,就可以知道某個動態(tài)表達(dá)式是否可以使用此規(guī)則所包容的語法樹。
“規(guī)則”是DLR緩存的主要對象。
前面介紹過的動態(tài)站點(diǎn)對象Target屬性所引用的委托是第一級緩存,它實(shí)現(xiàn)的處理邏輯是這樣的:
- //當(dāng)前處理規(guī)則,屬于第1級緩存
- if( d1 is int && d2 is int) //測試條件
- return (int)d1+(int)d2; //滿足測試條件,直接返回一個表達(dá)式樹
- //未命中,則在第2級、第3級緩存中查找,如果找到了,用找到的結(jié)果更新第1級緩存
- return site.Update(site,d1,d2);
如果3級緩存中都沒有命中的規(guī)則,則此動態(tài)站點(diǎn)所關(guān)聯(lián)的調(diào)用站點(diǎn)綁定對象會嘗試創(chuàng)建一個新的規(guī)則。如果創(chuàng)建新規(guī)則失敗,則由當(dāng)前編程語言(比如C#)所提供的默認(rèn)調(diào)用站點(diǎn)綁定對象決定如何處理,通常的作法是拋出一個異常。
當(dāng)前版本的DLR第2級緩存了10條規(guī)則,第3級則緩存了100條規(guī)則。
由于DLR自身設(shè)計(jì)了一個“規(guī)則”緩存系統(tǒng),又充分利用了CLR所提供的JIT緩存(因?yàn)樗袆討B(tài)調(diào)用代碼最終都會轉(zhuǎn)換為CLR可以執(zhí)行的IL指令,而CLR可以緩存這些代碼),使得動態(tài)代碼僅僅在第一次執(zhí)行時性能較差,后續(xù)的連續(xù)調(diào)用其性能可以逼近靜態(tài)代碼。
3 C# 4與動態(tài)語言的集成
由于幾乎所有的編程語言都可以使用抽象語法樹來表達(dá),因此,在理論上DLR支持無限多種編程語言間的互操作,在當(dāng)前版本中,可以實(shí)現(xiàn)C#/Visual Basic與IronPython和IronRuby的互操作,相信很快會出現(xiàn)其他動態(tài)編程語言的DLR實(shí)現(xiàn)。
一個有趣的地方是當(dāng)前基于DLR實(shí)現(xiàn)的動態(tài)編程語言都以“Iron”開頭,比如IronRuby和IronPython。IronPython的設(shè)計(jì)者、DLR的架構(gòu)設(shè)計(jì)師Jim Hugunin曾經(jīng)在微軟PDC 2008大會上解釋說主要是為了避免起一個“Python.NET”或“Python for .NET”之類“微軟味十足”的名字,才有了“IronPython”。他強(qiáng)調(diào):“Iron”系列動態(tài)語言將嚴(yán)格遵循動態(tài)語言自身的標(biāo)準(zhǔn)和規(guī)范,尊重這些動態(tài)語言已有的歷史和積累,不會引入一些僅限于.NET平臺的新語言特性,并且這些語言的.NET實(shí)現(xiàn)保持開源。與此同時,Jim Hugunin指出 “Iron”系列語言能很好地與.NET現(xiàn)有類庫、編程語言和工具集成,并且能“嵌入”到.NET宿主程序中。
(1)動態(tài)對象通訊協(xié)議
由于各種動態(tài)編程語言之間的特性相差極大,實(shí)現(xiàn)各語言間的互操作是個難題。為此DLR采取了一個聰明的策略,它不去嘗試設(shè)計(jì)一個“通用的類型系統(tǒng)”(CLR就是這么干的),而是設(shè)計(jì)了一個“通用的對象通訊協(xié)議”,規(guī)定所有需要互操作的動態(tài)對象必須實(shí)現(xiàn)IDynamicMetaObjectProvider接口,此接口定義了一個GetMetaObject()方法,接收一個語法樹對象作為參數(shù),向外界返回一個“動態(tài)元數(shù)據(jù)(DynamicMetaObject)”對象:
- DynamicMetaObject GetMetaObject(Expression parameter);
DynamicMetaObject對象向外界提供了兩個重要屬性:Restrictions引用一組測試條件,Expression屬性則引用一個語法樹。這兩個屬性組合起來就是可供動態(tài)站點(diǎn)對象緩存的“規(guī)則(Rule)”。
DLR中的“動態(tài)站點(diǎn)綁定對象(CallSiteBinder)”獲取了DynamicMetaObject對象之后,它調(diào)用此對象所提供的各個方法創(chuàng)建“規(guī)則”,讓“動態(tài)站點(diǎn)對象(CallSite<T>)”的Target屬性引用它,完成動態(tài)綁定的工作。
(2)動態(tài)語言集成環(huán)境
為了方便地實(shí)現(xiàn)靜態(tài)編程語言與各種動態(tài)編程語言間的相互集成,DLR提供了一整套稱為“通用寄宿(Common Hosting)”的組件,其中包容ScriptRuntime、ScriptScope等類型。
下面我們以IronPython為例,介紹如何在C# 4開發(fā)的程序中集成動態(tài)編程語言代碼。
首先需要創(chuàng)建一個ScriptRuntime對象,它是一個最頂層的對象,用于在一個.NET應(yīng)用程序域中“嵌入”一個特定動態(tài)語言的運(yùn)行環(huán)境:
- ScriptRuntime pythonRuntime = Python.CreateRuntime();
接著需要創(chuàng)建一個ScriptEngine對象,它是動態(tài)語言代碼的執(zhí)行引擎:
- ScriptEngine engine = pythonRuntime.GetEngine("py");
ScriptScope對象類似于C#中的命名空間,其中可以通過定義一些變量向動態(tài)代碼傳入數(shù)據(jù),比如下述代碼將一個C# 創(chuàng)建的ExpandoObject對象傳給Python代碼:
- ScriptScope scope = pythonRuntime.CreateScope();
- //C#創(chuàng)建動態(tài)對象
- dynamic expando = new ExpandoObject();
- expando.Name = "JinXuLiang"; //動態(tài)添加一個字段
- //讓IronPython接收C#創(chuàng)建的Expando對象
- scope.SetVariable("ExpandoObject", expando);
- string pythonCode = "print ExpandoObject.Name";
- //IronPython引擎執(zhí)行Python語句
- engine.CreateScriptSourceFromString(pythonCode).Execute(scope);
上述示例代碼是直接執(zhí)行Python代碼。在實(shí)際開發(fā)中,更常見的是直接執(zhí)行Python文件中的代碼,假設(shè)有一個Calculator.py文件,其中定義了一個Add函數(shù):
def Add(a,b):
return a+b
則以下C#代碼可以直接執(zhí)行之:
- ScriptRuntime pythonRuntime = Python.CreateRuntime();
- dynamic pythonFile = pythonRuntime.UseFile("Calculator.py");
- Console.WriteLine(pythonFile.Add(100, 200));
上述示例說明在DLR的支持之下,可以讓靜態(tài)編程語言使用動態(tài)語言所開發(fā)的庫,反過來,基于DLR實(shí)現(xiàn)的動態(tài)編程語言也能使用為靜態(tài)語言所設(shè)計(jì)的庫,比如標(biāo)準(zhǔn)的.NET基類庫。
這意味著兩點(diǎn):
(1)我們現(xiàn)在可以將“靜態(tài)”和“動態(tài)”編程語言組合起來,開發(fā)出一些具有高度交互性的應(yīng)用程序,使用靜態(tài)編程語言搭建系統(tǒng)框架,使用動態(tài)編程語言實(shí)現(xiàn)交互性,這是一個很值得注意的應(yīng)用領(lǐng)域。
(2)將來會出現(xiàn)一些“靜態(tài)”“動態(tài)”編程語言同時適用的庫,向?qū)崿F(xiàn)“無所不在的復(fù)用”目標(biāo)又前進(jìn)了一步。
Visual Studio 2010為新的.NET編程語言F#提供了專門的項(xiàng)目模板,但沒有為IronPython和IronRuby之類動態(tài)語言的開發(fā)提供支持,相信隨著動態(tài)語言在.NET平臺之上的應(yīng)用日趨廣泛,后繼版本的Visual Studio會直接支持動態(tài)語言的開發(fā)。
從C# 1.0~4.0所走過的路,可以很清晰地看到它的發(fā)展軌跡,得到這樣的一個結(jié)論:
未來的編程語言應(yīng)該是多范式的,具有高度的可組合性,在一個項(xiàng)目或產(chǎn)品中組合多個編程語言、使用多種編程范式會變得越來越普遍。
我們可以推斷C#的后繼版本將會在此條道路上越走越遠(yuǎn)……
原文標(biāo)題:C# 4動態(tài)編程新特性與DLR剖析
鏈接:http://www.cnblogs.com/bitfan/archive/2010/08/18/1802769.html
【編輯推薦】
- 詳解Visual C# 2010幾大新特征
- 詳解C#泛型特性及相關(guān)實(shí)例
- 詳解C#中相等運(yùn)算符重載可能造成的陷阱
- 事與愿違 開發(fā)者希望看到的C# 4.0新特性
- C#歷史回顧及C# 4.0新特性一覽