【飞秋】使用C# 4编写动态的代码
(1)使用C# 4編寫動(dòng)態(tài)的代碼
???????? C# 4新增了一個(gè)dynamic關(guān)鍵字,可以用它來編寫“動(dòng)態(tài)”的代碼。
???????? 例如,以下代碼創(chuàng)建了一個(gè)ExpandoObject對(duì)象(注意必須定義為dynamic):
?
??? dynamic dynamicObj = new ExpandoObject();
?
???????? 這一對(duì)象的奇特之處在于,我們可以隨時(shí)給它增加新成員:
???????
??? dynamicObj.Value = 100; //添加字段
??? dynamicObj.Increment = new Action(() => dynamicObj.Value++); //添加方法
?
???????? 這些動(dòng)態(tài)添加的成員與普通的類成員用法一樣:
即時(shí)通訊軟件
?
??? for (int i = 0; i < 10; i++)
??????? dynamicObj.Increment();//調(diào)用方法
??? Console.WriteLine("dynamicObj.Value={0}",dynamicObj.Value);//訪問字段
?
???????? ExpandoObject對(duì)象實(shí)現(xiàn)了IDictionary<string, object>接口,可看成是一個(gè)字典對(duì)象,所有動(dòng)態(tài)添加的成員都是這個(gè)字典對(duì)象中的元素,這意味我們不僅可以添加新成員,還可以隨時(shí)移除不再需要的成員:
?
??? //移除Increment方法
??? (dynamicObj as IDictionary<string, object>).Remove("Increment");
?
???????? 方法移除之后,再嘗試訪問此方法將引發(fā)RuntimeBinderException異常。
(2)使用dynamic關(guān)鍵字簡(jiǎn)化與COM組件交互的代碼
??? 要在.NET這個(gè)“托管世界”里調(diào)用“非托管世界”中的COM組件,我們必須通過 “互操作程序集(Interop Assembly)”作為橋梁,“互操作程序集”定義了CLR類型與COM類型之間的對(duì)應(yīng)關(guān)系。
???????? 只要給.NET項(xiàng)目添加對(duì)“互操作程序集”的引用,就可以在.NET應(yīng)用程序中創(chuàng)建這一程序集所包容的各種類型的實(shí)例(即COM包裝器對(duì)象),對(duì)這些對(duì)象的方法調(diào)用(或?qū)ζ鋵傩缘拇嫒?#xff09;將會(huì)被轉(zhuǎn)發(fā)給COM組件。
???????? 以調(diào)用Word為例,在C# 4.0之前您可能經(jīng)常需要編寫這樣的代碼:
?
??? Object wordapp = new Word.Application();?? //創(chuàng)建Word對(duì)象
??? 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);
?
???????? 上述對(duì)Open()方法的調(diào)用語句只能用“恐怖”一詞來形容,其原因是Word組件中的Open()方法定義了太多的參數(shù)。
???????? C#4使用dynamic關(guān)鍵字,配合從Visual Basic中學(xué)來的“命名參數(shù)與可選參數(shù)”這兩個(gè)新語法特性,可以寫出更簡(jiǎn)潔的代碼:
?
??? dynamic wordapp = new Word.Application();
??? dynamic doc = wordapp.Documents.Open(FileName: “MyDoc.docx”);
?
???????? 上述代碼中省去了用不著的參數(shù),并且可以去掉參數(shù)前的ref關(guān)鍵字。
???????? 當(dāng)上述代碼運(yùn)行時(shí),DLR會(huì)使用反射技術(shù)將dynamic表達(dá)式“綁定(bind)”到COM互操作程序集中所包容的Word.Application代理對(duì)象。
(3)C# 4動(dòng)態(tài)編程技術(shù)內(nèi)幕
???????? C#4中所定義的dynamic變量可以引用以下類型的對(duì)象:
l 傳統(tǒng)的“靜態(tài)”的CLR對(duì)象。
l COM包裝器對(duì)象。前面已經(jīng)介紹了這方面的內(nèi)容。
l 實(shí)現(xiàn)了IDynamicMetaObjectProvider接口的“動(dòng)態(tài)對(duì)象”,ExpandoObject就是這種類型對(duì)象的實(shí)例。
l 基于DLR實(shí)現(xiàn)的動(dòng)態(tài)語言(比如IronRuby和IronPython)所創(chuàng)建的對(duì)象。
???????? 從C#程序員角度來看,所有這四種對(duì)象都是一樣的,都可用一個(gè)dynamic變量引用之,而DLR在程序運(yùn)行時(shí)動(dòng)態(tài)地將方法調(diào)用和字段存取請(qǐng)求“綁定”到真正的對(duì)象上。
???????? dynamic的功能是由DLR所支撐的,是C#編譯器與DLR分工合作的成果。
???????? 請(qǐng)看以下示例代碼:
?
??? dynamic d = 100;
??? d++;
?
???????? C#編譯器在處理上述代碼時(shí),它并不去檢查變量d是否可以支持自增操作,而是為其創(chuàng)建了一個(gè)CallSite<T>對(duì)象(<>p__Site1):
?
??? private static class <Main>o__SiteContainer0 {
??????? public static CallSite<Func<CallSite, object, object>> <>p__Site1;
??? }
?
???????? 中文MSDN將CallSite<T>譯為“動(dòng)態(tài)(調(diào)用)站點(diǎn)”,它是DLR中的核心組件之一。
???????? 動(dòng)態(tài)站點(diǎn)對(duì)象通過CallSite<T>.Create()方法創(chuàng)建, C#編譯器會(huì)為其指定一個(gè)派生自CallSiteBinder的對(duì)象(稱為“動(dòng)態(tài)站點(diǎn)綁定對(duì)象”)作為其參數(shù)。
???????? 動(dòng)態(tài)站點(diǎn)綁定對(duì)象是與具體語言相關(guān)的,比如IronPython和C#都有各自的動(dòng)態(tài)站點(diǎn)綁定對(duì)象。
???????? 動(dòng)態(tài)站點(diǎn)綁定對(duì)象的主要工作是將代碼中的動(dòng)態(tài)表達(dá)式(本例中為d++)轉(zhuǎn)換為一棵“抽象語法樹(AST:Abstract Syntax Tree)”,這棵語法樹被稱為“DLR Tree”,是在.NET 3.5所引入的LINQ表達(dá)式樹的基礎(chǔ)上擴(kuò)充而來的,因此,有時(shí)又稱其為“表達(dá)式樹(Expression Tree)”
???????? DLR在內(nèi)部調(diào)用此表達(dá)式樹的Compile()方法生成IL指令,得到一個(gè)可以被CLR所執(zhí)行的委托(在本例中其類型就是Func<CallSite, object, object>)。
???????? 動(dòng)態(tài)調(diào)用站點(diǎn)對(duì)象(本例中為<>p__Site1)有一個(gè)Target屬性,它負(fù)責(zé)引用這一生成好的委托。
???????? 委托生成之后,動(dòng)態(tài)表達(dá)式的執(zhí)行就體現(xiàn)為委托的執(zhí)行,其實(shí)參由C#編譯器直接“寫死”在IL代碼中。
???????? 簡(jiǎn)化的代碼示意如下(通過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)行時(shí)完成的。
(4)動(dòng)態(tài)代碼很慢嗎?
???????? 動(dòng)態(tài)編程語言易學(xué)易用,代碼緊湊,開發(fā)靈活,但性能則一直是它的“軟肋”。為了提升性能,DLR設(shè)計(jì)了一個(gè)三級(jí)緩存策略。
???????? 動(dòng)態(tài)站點(diǎn)綁定對(duì)象會(huì)為動(dòng)態(tài)調(diào)用表達(dá)式轉(zhuǎn)換而成的語法樹加上相應(yīng)的測(cè)試條件(稱為“test”),構(gòu)成一個(gè)“規(guī)則(Rule)”,這個(gè)規(guī)則可以用于判斷某個(gè)語法樹是否可用于特定的動(dòng)態(tài)調(diào)用表達(dá)式。
???????? 舉個(gè)例子,請(qǐng)看以下這個(gè)動(dòng)態(tài)表達(dá)式:
?
??? d1 + d2
?
???????? 如果在程序運(yùn)行時(shí)d1和d2都是int類型的整數(shù),則DLR生成的規(guī)則為:
?????
??? if( d1 is int && d2 is int) //測(cè)試條件
??????? return (int)d1+(int)d2; //語法樹
?
???????? DLR通過檢查規(guī)則中的“測(cè)試條件”,就可以知道某個(gè)動(dòng)態(tài)表達(dá)式是否可以使用此規(guī)則所包容的語法樹。
???????? “規(guī)則”是DLR緩存的主要對(duì)象。
???????? 前面介紹過的動(dòng)態(tài)站點(diǎn)對(duì)象Target屬性所引用的委托是第一級(jí)緩存,它實(shí)現(xiàn)的處理邏輯是這樣的:
?
??? //當(dāng)前處理規(guī)則,屬于第1級(jí)緩存
??? if( d1 is int && d2 is int) //測(cè)試條件
??????? return (int)d1+(int)d2; //滿足測(cè)試條件,直接返回一個(gè)表達(dá)式樹
??? //未命中,則在第2級(jí)、第3級(jí)緩存中查找,如果找到了,用找到的結(jié)果更新第1級(jí)緩存
??? return site.Update(site,d1,d2);
?
???????? 如果3級(jí)緩存中都沒有命中的規(guī)則,則此動(dòng)態(tài)站點(diǎn)所關(guān)聯(lián)的調(diào)用站點(diǎn)綁定對(duì)象會(huì)嘗試創(chuàng)建一個(gè)新的規(guī)則。如果創(chuàng)建新規(guī)則失敗,則由當(dāng)前編程語言(比如C#)所提供的默認(rèn)調(diào)用站點(diǎn)綁定對(duì)象決定如何處理,通常的作法是拋出一個(gè)異常。
???????? 當(dāng)前版本的DLR第2級(jí)緩存了10條規(guī)則,第3級(jí)則緩存了100條規(guī)則。
??????? 由于DLR自身設(shè)計(jì)了一個(gè)“規(guī)則”緩存系統(tǒng),又充分利用了CLR所提供的JIT緩存(因?yàn)樗袆?dòng)態(tài)調(diào)用代碼最終都會(huì)轉(zhuǎn)換為CLR可以執(zhí)行的IL指令,而CLR可以緩存這些代碼),使得動(dòng)態(tài)代碼僅僅在第一次執(zhí)行時(shí)性能較差,后續(xù)的連續(xù)調(diào)用其性能可以逼近靜態(tài)代碼。
3 C# 4與動(dòng)態(tài)語言的集成
???????? 由于幾乎所有的編程語言都可以使用抽象語法樹來表達(dá),因此,在理論上DLR支持無限多種編程語言間的互操作,在當(dāng)前版本中,可以實(shí)現(xiàn)C#/Visual Basic與IronPython和IronRuby的互操作,相信很快會(huì)出現(xiàn)其他動(dòng)態(tài)編程語言的DLR實(shí)現(xiàn)。
???????? 一個(gè)有趣的地方是當(dāng)前基于DLR實(shí)現(xiàn)的動(dòng)態(tài)編程語言都以“Iron”開頭,比如IronRuby和IronPython。IronPython的設(shè)計(jì)者、DLR的架構(gòu)設(shè)計(jì)師Jim Hugunin曾經(jīng)在微軟PDC 2008大會(huì)上解釋說主要是為了避免起一個(gè)“Python.NET”或“Python for .NET”之類“微軟味十足”的名字,才有了“IronPython”。他強(qiáng)調(diào):“Iron”系列動(dòng)態(tài)語言將嚴(yán)格遵循動(dòng)態(tài)語言自身的標(biāo)準(zhǔn)和規(guī)范,尊重這些動(dòng)態(tài)語言已有的歷史和積累,不會(huì)引入一些僅限于.NET平臺(tái)的新語言特性,并且這些語言的.NET實(shí)現(xiàn)保持開源。與此同時(shí),Jim Hugunin指出 “Iron”系列語言能很好地與.NET現(xiàn)有類庫(kù)、編程語言和工具集成,并且能“嵌入”到.NET宿主程序中。
(1)動(dòng)態(tài)對(duì)象通訊協(xié)議
???????? 由于各種動(dòng)態(tài)編程語言之間的特性相差極大,實(shí)現(xiàn)各語言間的互操作是個(gè)難題。為此DLR采取了一個(gè)聰明的策略,它不去嘗試設(shè)計(jì)一個(gè)“通用的類型系統(tǒng)”(CLR就是這么干的),而是設(shè)計(jì)了一個(gè)“通用的對(duì)象通訊協(xié)議”,規(guī)定所有需要互操作的動(dòng)態(tài)對(duì)象必須實(shí)現(xiàn)IDynamicMetaObjectProvider接口,此接口定義了一個(gè)GetMetaObject()方法,接收一個(gè)語法樹對(duì)象作為參數(shù),向外界返回一個(gè)“動(dòng)態(tài)元數(shù)據(jù)(DynamicMetaObject)”對(duì)象:
?
??????? DynamicMetaObject GetMetaObject(Expression parameter);
?
?? DynamicMetaObject對(duì)象向外界提供了兩個(gè)重要屬性:Restrictions引用一組測(cè)試條件,Expression屬性則引用一個(gè)語法樹。這兩個(gè)屬性組合起來就是可供動(dòng)態(tài)站點(diǎn)對(duì)象緩存的“規(guī)則(Rule)”。
???????? DLR中的“動(dòng)態(tài)站點(diǎn)綁定對(duì)象(CallSiteBinder)”獲取了DynamicMetaObject對(duì)象之后,它調(diào)用此對(duì)象所提供的各個(gè)方法創(chuàng)建“規(guī)則”,讓“動(dòng)態(tài)站點(diǎn)對(duì)象(CallSite<T>)”的Target屬性引用它,完成動(dòng)態(tài)綁定的工作。
(2)動(dòng)態(tài)語言集成環(huán)境
???????? 為了方便地實(shí)現(xiàn)靜態(tài)編程語言與各種動(dòng)態(tài)編程語言間的相互集成,DLR提供了一整套稱為“通用寄宿(Common Hosting)”的組件,其中包容ScriptRuntime、ScriptScope等類型。
???????? 下面我們以IronPython為例,介紹如何在C# 4開發(fā)的程序中集成動(dòng)態(tài)編程語言代碼。
???????? 首先需要?jiǎng)?chuàng)建一個(gè)ScriptRuntime對(duì)象,它是一個(gè)最頂層的對(duì)象,用于在一個(gè).NET應(yīng)用程序域中“嵌入”一個(gè)特定動(dòng)態(tài)語言的運(yùn)行環(huán)境:
?
??? ScriptRuntime pythonRuntime = Python.CreateRuntime();
?
???????? 接著需要?jiǎng)?chuàng)建一個(gè)ScriptEngine對(duì)象,它是動(dòng)態(tài)語言代碼的執(zhí)行引擎:
?
??? ScriptEngine engine = pythonRuntime.GetEngine("py");
?
???????? ScriptScope對(duì)象類似于C#中的命名空間,其中可以通過定義一些變量向動(dòng)態(tài)代碼傳入數(shù)據(jù),比如下述代碼將一個(gè)C# 創(chuàng)建的ExpandoObject對(duì)象傳給Python代碼:
?
??? ScriptScope scope = pythonRuntime.CreateScope();
??? //C#創(chuàng)建動(dòng)態(tài)對(duì)象
???? dynamic expando = new ExpandoObject();
??? expando.Name = "JinXuLiang"; //動(dòng)態(tài)添加一個(gè)字段
???? //讓IronPython接收C#創(chuàng)建的Expando對(duì)象
??? scope.SetVariable("ExpandoObject", expando);
??? string pythonCode = "print ExpandoObject.Name";
??? //IronPython引擎執(zhí)行Python語句
??? engine.CreateScriptSourceFromString(pythonCode).Execute(scope);??????????
?
???????? 上述示例代碼是直接執(zhí)行Python代碼。在實(shí)際開發(fā)中,更常見的是直接執(zhí)行Python文件中的代碼,假設(shè)有一個(gè)Calculator.py文件,其中定義了一個(gè)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)編程語言使用動(dòng)態(tài)語言所開發(fā)的庫(kù),反過來,基于DLR實(shí)現(xiàn)的動(dòng)態(tài)編程語言也能使用為靜態(tài)語言所設(shè)計(jì)的庫(kù),比如標(biāo)準(zhǔn)的.NET基類庫(kù)。
???????? 這意味著兩點(diǎn):
???????? (1)我們現(xiàn)在可以將“靜態(tài)”和“動(dòng)態(tài)”編程語言組合起來,開發(fā)出一些具有高度交互性的應(yīng)用程序,使用靜態(tài)編程語言搭建系統(tǒng)框架,使用動(dòng)態(tài)編程語言實(shí)現(xiàn)交互性,這是一個(gè)很值得注意的應(yīng)用領(lǐng)域。
???????? (2)將來會(huì)出現(xiàn)一些“靜態(tài)”“動(dòng)態(tài)”編程語言同時(shí)適用的庫(kù),向?qū)崿F(xiàn)“無所不在的復(fù)用”目標(biāo)又前進(jìn)了一步。
???????? Visual Studio 2010為新的.NET編程語言F#提供了專門的項(xiàng)目模板,但沒有為IronPython和IronRuby之類動(dòng)態(tài)語言的開發(fā)提供支持,相信隨著動(dòng)態(tài)語言在.NET平臺(tái)之上的應(yīng)用日趨廣泛,后繼版本的Visual Studio會(huì)直接支持動(dòng)態(tài)語言的開發(fā)。
?
?
?
關(guān)注技術(shù)文章飛秋:http://www.freeeim.com/,24小時(shí)專業(yè)轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的【飞秋】使用C# 4编写动态的代码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我也聊聊串口通信协议:用户层通信协议的编
- 下一篇: 我也聊聊串口通信协议:数据包校验与常用校