【飞秋】使用C# 4编写动态的代码
(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代理對象。
(3)C# 4動態編程技術內幕
???????? C#4中所定義的dynamic變量可以引用以下類型的對象:
l 傳統的“靜態”的CLR對象。
l COM包裝器對象。前面已經介紹了這方面的內容。
l 實現了IDynamicMetaObjectProvider接口的“動態對象”,ExpandoObject就是這種類型對象的實例。
l 基于DLR實現的動態語言(比如IronRuby和IronPython)所創建的對象。
???????? 從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;
??? }
?
???????? 中文MSDN將CallSite<T>譯為“動態(調用)站點”,它是DLR中的核心組件之一。
???????? 動態站點對象通過CallSite<T>.Create()方法創建, C#編譯器會為其指定一個派生自CallSiteBinder的對象(稱為“動態站點綁定對象”)作為其參數。
???????? 動態站點綁定對象是與具體語言相關的,比如IronPython和C#都有各自的動態站點綁定對象。
???????? 動態站點綁定對象的主要工作是將代碼中的動態表達式(本例中為d++)轉換為一棵“抽象語法樹(AST:Abstract 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
?
???????? 如果在程序運行時d1和d2都是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#)所提供的默認調用站點綁定對象決定如何處理,通常的作法是拋出一個異常。
???????? 當前版本的DLR第2級緩存了10條規則,第3級則緩存了100條規則。
??????? 由于DLR自身設計了一個“規則”緩存系統,又充分利用了CLR所提供的JIT緩存(因為所有動態調用代碼最終都會轉換為CLR可以執行的IL指令,而CLR可以緩存這些代碼),使得動態代碼僅僅在第一次執行時性能較差,后續的連續調用其性能可以逼近靜態代碼。
3 C# 4與動態語言的集成
???????? 由于幾乎所有的編程語言都可以使用抽象語法樹來表達,因此,在理論上DLR支持無限多種編程語言間的互操作,在當前版本中,可以實現C#/Visual Basic與IronPython和IronRuby的互操作,相信很快會出現其他動態編程語言的DLR實現。
???????? 一個有趣的地方是當前基于DLR實現的動態編程語言都以“Iron”開頭,比如IronRuby和IronPython。IronPython的設計者、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)”的組件,其中包容ScriptRuntime、ScriptScope等類型。
???????? 下面我們以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#提供了專門的項目模板,但沒有為IronPython和IronRuby之類動態語言的開發提供支持,相信隨著動態語言在.NET平臺之上的應用日趨廣泛,后繼版本的Visual Studio會直接支持動態語言的開發。
?
?
?
關注技術文章飛秋:http://www.freeeim.com/,24小時專業轉載。
總結
以上是生活随笔為你收集整理的【飞秋】使用C# 4编写动态的代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我也聊聊串口通信协议:用户层通信协议的编
- 下一篇: 我也聊聊串口通信协议:数据包校验与常用校