細說CodeDom
在上一篇文章中,老周厚着臉皮給大伙介紹了代碼文檔的基本結構,以及一些代碼對象與CodeDom類型的對應關系。
在評論中老周看到有朋友提到了 Emit,那老周就順便提一下。嚴格上說,Emit並不是針對代碼文檔生成和編譯而設計的,Emit一方面可以實時發出 IL 指令,另一方面也支持動態程序集,即可以在運行時創建程序集,並可以定義類型,然后可以執行。而CodeDom所針對的是代碼文檔的生成和編譯,所以說,是有所不同的。
哦,是了,還有一個玩意兒挺有趣,也提一下吧——動態 Linq 表達式樹。它也跟動態編譯有點像,就是動態創建 LINQ表達式樹,LINQ懂吧,別告訴你不知道,這是玩.net的必備法寶,表達式樹創建后會實時編譯為一個委托實例,使用時直接調用生成的委托實例即可。
好,下面開始本文的內容。先說說表達式,因為語句是由表達式組成的,按照正常人類的思考方式,應當由小及大來學習。啥是表達式呢,其實可以說,表達式是代碼文檔的基礎元素,比如一個int值 2500,就是一個表達式;字符串常量用雙引號包起來,如"abc",也是一個表達式;當前類實例的引用 this也是表達式;基類實例的引用 base,也是表達式;變量名 a 也是表達式;數組索引,如 [0] 也是表達式;方法中的輸出參數 out 也是表達式……
CodeExpression 是所有表達式對象的公共基類,從它的派生類來看,咱們不妨對表達式的類型先來個非專業總結,這樣有助於大家掌握思路。這個類的派生類相當多,不要暈,思路理清了,就不怕它數量多。
老周大致把這些表達式類划分以下幾類(僅供參考):
1、創建實例。如CodeArrayCreateExpression、CodeDelegateCreateExpression等,大家可以根據它們的名字來猜猜其作用,現在你不必弄明白到底怎么用,后面老周會教你怎么用的。
2、引用。比如當前實例引用(this)CodeThisReferenceExpression,再比如引用某個實例的方法的語句 CodeMethodReferenceExpression, 像 x.Run(……)。再比如屬性里面set訪問器中,大家都知道有一個 value 關鍵字,要引用它可以用 CodePropertySetValueReferenceExpression 類。
3、運算符(操作符)。這好懂了吧,+-*/=()==!<>這些都是操作符。對於二進制運算,可以用CodeBinaryOperatorExpression,它通過 CodeBinaryOperatorType 枚舉來規范要用到的運算符,如加法、減法等。如果要進行類型轉換,可以使用類型轉換運算符 CodeCastExpression,它也是一種表達式。如果想用 typeof 運算符,可以使用 CodeTypeOfExpression 類。
4、其他。比較零碎。像參數引用,數組索引等。
很多表達式類在使用時,直接賦相應的值就行,用習慣了之后也不會很難。在舉例之前,老周先講一下如何生成代碼,相信大伙在做完例子后,最迫切的就是想看看生成的代碼是啥樣子的。
生成代碼要用 CodeDomProvider 類(位於System.CodeDom.Compiler命名空間下),這個類不用 new 的,它有一個靜態的 CreateProvider 方法,調用后直接返回 CodeDomProvider 實例,調用方法時,可以用一個字符來指定編程語言。比如,C#、cs、CSharp都可以表示C#語言;用 VB、VisualBasic都表示VB語言;用js、JScript表示jscript語言;用cpp表示C++語言(托管)。名字是不區分大小寫的,所以,CS和cs都一樣。
然后,你會看到這個類有一堆GenerateCodeFromXXXX的方法,這些XXXX可以是表達式、語句、編譯單元、類型定義等。生成代碼時,你必須提供一個 TextWriter,代碼生成后會寫進這個writer里面。
下面給大家演示一下,咱們就把代碼輸出到控制台吧,這樣馬上就能看到效果。
假設我定義一個類型,名叫 Dog,它是結構。來,看代碼:
CodeTypeDeclaration dcl = new CodeTypeDeclaration("Dog"); dcl.IsStruct = true; dcl.Attributes = MemberAttributes.Public;
IsStruct 指明它是個結構,CodeTypeDeclaration類構造函數中傳的是類型的名字,叫Dog。Attributes屬性設置這個類型為公共類型(public)。
好,現在我們就定義好一個類型了,下面咱們來生成C#、VB和C++三種語言的代碼。
// 生成C#代碼 CodeDomProvider provider = CodeDomProvider.CreateProvider("cs"); Console.WriteLine("生成 C# 代碼:"); provider.GenerateCodeFromType(dcl, Console.Out, null); // 生成 VB 代碼 provider = CodeDomProvider.CreateProvider("vb"); Console.WriteLine("\n生成 VB 代碼:"); provider.GenerateCodeFromType(dcl, Console.Out, null); // 生成 C++ 代碼 provider = CodeDomProvider.CreateProvider("cpp"); Console.WriteLine("\n生成 C++ 代碼:"); provider.GenerateCodeFromType(dcl, Console.Out, null);
運行后,就會看到以下內容。
方法很簡單,先用CreateProvider靜態方法獲取特定語言的提供程序,然后 GenerateCodeFromXXXXX,你要從什么代碼對象生成,就調用哪個版本,比如要從編譯單元生成代碼,就應當調用GenerateCodeFromCompileUnit方法,要從定義的命名空間生成代碼就調用GenerateCodeFromNamespace方法。
好,咱們開始練習,先來個操作符的,下面例子用來生成 a + b,a和b是變量名,我們先不管變量是哪來的,反正目的是使用操作符。
CodeVariableReferenceExpression left = new CodeVariableReferenceExpression("a"); CodeVariableReferenceExpression right = new CodeVariableReferenceExpression("b"); CodeBinaryOperatorExpression opt = new CodeBinaryOperatorExpression(); opt.Operator = CodeBinaryOperatorType.Add; opt.Left = left; opt.Right = right;
一個操作符表達式,通常會三個組成元素——運算符,左邊操作數,右邊操作數。在這個例子里面,左邊和右邊分別使用變量引用,即用到 CodeVariableReferenceExpression 類,這個類用法也簡單,只要提供一個變量名稱就行了。
最后生成代碼為:
(a + b)
再看一個例子。
CodeThisReferenceExpression thisexpr = new CodeThisReferenceExpression(); CodeFieldReferenceExpression fexp = new CodeFieldReferenceExpression(); fexp.FieldName = "m_val"; fexp.TargetObject = thisexpr; CodeBinaryOperatorExpression opt = new CodeBinaryOperatorExpression(); opt.Left = fexp; opt.Right = new CodePrimitiveExpression((int)300); opt.Operator = CodeBinaryOperatorType.Assign;
上面例子中的 CodeBinaryOperatorExpression 對象指定運算符為 Assign, 即賦值符號(=)。然后我們再看它兩邊的操作數。左邊引用的是當前實例的字段,首先要創建一個CodeThisReferenceExpression,這個類不需要指定任何參數,因為它生成的就是this關鍵字,然后用CodeFieldReferenceExpression來引用this實例中一個叫m_val的字段;右邊操作數是一個常量,常量值可以用CodePrimitiveExpression來表示。
CodePrimitiveExpression 一般用於指定基礎類型的常量值,如int、string、double等。如這樣
CodePrimitiveExpression p = new CodePrimitiveExpression(0.1322d);
生成代碼后,會自動將傳入的值表示為double類型常量。生成代碼如下:
0.1322D
再比如
CodePrimitiveExpression p = new CodePrimitiveExpression("ask");
就會生成字符串常量:
"ask"
好了,上面的賦值表達式最終得到的結果如下:
(this.m_val = 300)
下面咱們來生成一句 typeof表達式。
CodeTypeOfExpression texp = new CodeTypeOfExpression(typeof(string)); Console.WriteLine("C# 代碼:"); CodeDomProvider prd = CodeDomProvider.CreateProvider("cs"); prd.GenerateCodeFromExpression(texp, Console.Out, null); Console.WriteLine("\n"); Console.WriteLine("VB 代碼:"); CodeDomProvider prd2 = CodeDomProvider.CreateProvider("vb"); prd2.GenerateCodeFromExpression(texp, Console.Out, null);
很簡單,實例化 CodeTypeOfExpression 時,把某個類型的type傳進去就行了。
最后輸出的代碼如下:
C# 代碼: typeof(string) VB 代碼: GetType(String)
咱們再來個類型轉換的表達式。
CodeVariableReferenceExpression vexp = new CodeVariableReferenceExpression(); vexp.VariableName = "x"; CodeTypeReference tref = new CodeTypeReference(typeof(decimal)); CodeCastExpression cexp = new CodeCastExpression(); cexp.Expression = vexp; cexp.TargetType = tref;
CodeVariableReferenceExpression主要設置兩個參數,Expression 指的是要進行類型轉換的對象,通常是一個變量;TargetType是要轉換的目標類型,需要用一個CodeTypeReference來封裝,使用時直接把類型的type傳遞即可。
類型轉換表達式生成代碼如下:
((decimal)(x))
=================================================
學會使用表達式后,語句就好辦了,因為語句就是由表達式組成的,只是為了說明是語句,在C類風格的語言中會以英文的分號結尾(VB除外)。
來,來一句賦值語句。
CodeVariableReferenceExpression left = new CodeVariableReferenceExpression("f"); CodePrimitiveExpression sexp = new CodePrimitiveExpression("so hot"); CodeAssignStatement assstm = new CodeAssignStatement(); assstm.Left = left; assstm.Right = sexp;
CodeAssignStatement 和剛才的賦值表達式很像,也需要指定左邊的表達式和右邊的表達式。最后生成的代碼如下:
f = "so hot";
大家看到了,語句結尾是有分號的,剛才的表達式是沒有分號的。
接下來,咱們聲明一個變量,然后給它一個值。
CodeVariableDeclarationStatement decl = new CodeVariableDeclarationStatement(typeof(int), "n"); CodeAssignStatement ass = new CodeAssignStatement(); ass.Left = new CodeVariableReferenceExpression("n"); ass.Right = new CodePrimitiveExpression(98000); CodeDomProvider prd = CodeDomProvider.CreateProvider("cs"); prd.GenerateCodeFromStatement(decl, Console.Out, null); prd.GenerateCodeFromStatement(ass, Console.Out, null);
這段實際上是生成了兩句代碼,第一句是聲明語句,CodeVariableDeclarationStatement將產生聲明變量的語句,需要指定變量的類型和變量名。
第二句是賦值語句,需要指定左邊和右邊。左邊引用變量n,右邊是常量值。
生成代碼如下:
int n; n = 98000;
以上代碼不夠簡潔,我們完全可以在聲明變量的時候,就將它初始化,這樣只用一個語句就可以了。
CodeVariableDeclarationStatement decl = new CodeVariableDeclarationStatement(typeof(int), "n", new CodePrimitiveExpression(98000));
這樣一個語句就完成了,生成的代碼如下:
int n = 98000;
這里老周不會將所有語句一個個做介紹。本文介紹的這些表達式和語句,主要是幫助初學者朋友們練手,以便找到感覺,剩下的一些復雜的語句——如選擇語句、循環語句這些,老周在后面的文章中會介紹的。
OK,今天的牛皮就吹到這里,希望對各位有幫助。