动态生成与编译(三)----写一个面向过程的程序

来源:百度文库 编辑:神马文学网 时间:2024/04/27 10:11:12

动态生成与编译(三)----写一个面向过程的程序

 

先讲一个简单的程序,把变量声明、赋值、if语句、for循环等讲掉,这些是程序的基本的东东,再顺便带一下异常处理。就是一个比(一)复杂一点的控制台程序而已,关于类方面的东西下次再来。

找来找去找了个Fibonacci数列的程序,这个输入输出比较的简单,而且基本流程代码都有。当然真的实际生成不会去生成这种的程序,现在主要是“借”它一用。

先看生成的程序,下面就是用CodeDOM生成的代码:

namespace Sample {

    using System;

   

   

public class DemoClass {

       public static void Main() {

            System.Console.WriteLine("输入 n的值:");

            string Nstr = System.Console.In.ReadLine();

            try {

                int N = System.Convert.ToInt32(Nstr);

                if ((N >= 1)) {

                    Fibonc(N);

                }

                else {

                    System.Console.WriteLine("n 必须大于0");

                }

            }

            catch (System.Exception ex) {

                System.Console.WriteLine(ex.Message);

            }

            System.Console.Read();

        }

       

        // 求Fibonacci数列

        private static void Fibonc(int n) {

            int F;

            int F1 = 0;

            int F2 = 1;

            for (int i = 1; (i <= n); i = (i + 1)){

                F = (F1 + F2);

                System.Console.WriteLine("{0},Fibonacci:{1}", i, F);

                F1 = F2;

                F2 = F;

            }

     }

}

}

因为一直找不到生成while循环的方法(用for代替当然也可以,但看起来比较怪怪的),所以程序比较的弱智点,错了就得重来。

 

那个Main()先放着,先看下面这个求Fibonacci数列的方法,先要把它定义出来,

                CodeMemberMethod FiboncMethod = new CodeMemberMethod();

                FiboncMethod.Comments.Add(new CodeCommentStatement("求Fibonacci数列"));

                FiboncMethod.Name = "Fibonc";//方法名

                FiboncMethod.Attributes =  MemberAttributes.Private | MemberAttributes.Static;//可见性

                FiboncMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int),"n"));//参数

 

任何一个方法总归是要有归宿的,所以它是MemberMethod,是属于某个Type的Member,在(二)中讲过,CodeMemberMethod是从CodeTypeMember继承来的。new出来,加注释,取名,设置可见性(到处属性属性的人都要晕了,用可见性比较好)都没什么好说的。

最后加参数出来一个新东西CodeParameterDeclarationExpression,好长呀,直译叫参数声明表达式,它的构造函数一看就明白,定义了类型跟名字。在CodeDOM里很多类的构造函数都是有好几种形式的,象上面这个就有3种(Type,string)、(string,string)和(CodeTypeReference,string)。

上面程序中用的就是(Type,string)这种形式了;第二种是直接用类型名来代替,不过要注意对于象int这样的基础类型如果写成("int","n")它会生成Fibonc(@int n),显然这不是我们想要的,它应该写成("System.Int32","n");至于第三种在这里没有任何的吸引力要这样写(new CodeTypeReference("System.Int32"),"n")) 太累了,用在那种数据类型也是用代码生成的情况下比较的有用。

 

定义好了方法,一开始就是变量声明,变量声明用的是CodeVariableDeclarationStatement 如int F2 = 1;这一句是这样来的:

CodeVariableDeclarationStatement VarF2 = new CodeVariableDeclarationStatement(typeof(int),"F2",new CodePrimitiveExpression(1));

 

这个跟参数声明差不多,唯一不同的是变量声明有个赋初始值问题,当然不象上面那样在构造函数里赋初始值,而是设置VarF2的InitExpression属性也是能产生一样的效果的。InitExprsssion属性是CodeExpression类型的,在(二)说到过这个CodeExpression是很丰富的东西,上面的CodePrimitiveExpression也是CodeExpression的一种,Primitive的意思就是“原始的”的意思,它把括号里的东西原样的拿出来,它能表示各种整数、各种浮点数、字符串及空引用(null或Nothing)。

 

下面的for循环比较的麻烦,把它分解出来一点一点的讲。

循环的初始化部分是一个变量声明语句,声明了一个i并赋初值为1,它的写法上面说过了,略过。第二部分测试表达式要用到一个新的CodeExpression,CodeBinaryOperatorExpression:

CodeBinaryOperatorExpression test = new CodeBinaryOperatorExpression(new CodeVariableReferenceExpression("i"),

                 CodeBinaryOperatorType.LessThanOrEqual,new CodeArgumentReferenceExpression("n"));

 

它的构造函数看起来很长的样子,其实是很容易理解的,三个参数分别是左表达式、运算符、右表达式。这里左边是个变量i,所以用上了CodeVariableReferenceExpression(就叫变量引用了),右边是个方法带的参数n, 用CodeArgumentReferenceExpression(那就是参数引用了)来取。中间那个表示运算符的CodeBinaryOperatorType是个枚举,什么加减乘除、大小等于、与或之类的都有。(不过这个CodeBinaryOperatorExpression是表示二元运算的,到现在还没找到一元运算应该怎么办,比如取反,这个运算还是有点用的)

第三部分的i = (i + 1)是一个赋值语句,赋值语句用CodeAssignStatement来产生,它比运算表达式简单那么一点点,左右都是有的,只是中间的运算符没了。如下:

CodeAssignStatement increment = new CodeAssignStatement(new CodeVariableReferenceExpression("i"), new CodeBinaryOperatorExpression(。。。));

这个赋值语句左边是个变量,右边同时又是一个运算表达式,所以又new 了一个CodeBinaryOperatorExpression,全写出来太长了,这里略。

for循环完了,没有,只是上头好了,循环体内还有四句呢。在CodeDOM里,整个for循环的所有语句都是要组成一个大的CodeStatement的。在循环体内第一、三、四句都是赋值语句,不进了。讲讲第二句,方法调用。方法调用表达式CodeMethodInvokeExpression是CodeDOM里使用频率非常高的一个CodeExpression,现在写程序没有可能会一路走到底的,总要会调用到方法。CodeMethodInvokeExpression的一个构造函数如下:

public CodeMethodInvokeExpression(
   CodeExpression targetObject,
   string methodName,
   params CodeExpression[] parameters
);

第一个参数是调用方法的目标对象,是一个CodeExpression,在这个程序里用的是CodeTypeReferenceExpression(注意它与CodeTypeReference的区别,其实如果直接看中文的文档的解释那简直是云里雾里的,还是直接从字面上理解省事,其中一个是Expression,一个不是,二者用的地方不一样,其实是差不多的东西,我这是这样理解的)。第二个参数没什么好说的,一看就明白了。第三个参数是个数组,CodeExpression的数组,调用的方法有几个参数,这数组里就有几个CodeExpression,方法无参的话这个参数可省略(文档里没提到这点,刚开始以为要new 一个空数组在那里的,后来发现不用。如这个程序里的WriteLine有三个参数,那就有三个CodeExpression,分别是一个CodePrimitiveExpression、二个CodeVariableReferenceExpression

在(二)里说过CodeExpression一般情况下是不能当语句的,所以上面的那个CodeMethodInvokeExpression要转一下,用CodeExpressionStatement包一下就好了(直接把上面产生的CodeMethodInvokeExpression放到CodeExpressionStatement构造函数里就OK了)。

循环的每一部分都分解完了,现在大家伙要登场了。产生for循环语句的CodeStatement----CodeIterationStatement是CodeDOM里最为复杂的了,有四个参数之多,其中有一个参数还是数组。如下的形式:

CodeIterationStatement forloop = new CodeIterationStatement(

Vari,//初始表达式,即声明i,一个CodeVariableDeclarationStatement

test,//循环测试,就是一个CodeBinaryOperatorExpression

increment,//循环递增的语句,就是一个CodeAssignStatement

new CodeStatement[] {。。。}//循环体内的语句,这里略

);

 

讲了这么多,主题不要忘了,这些上面的CodeStatement都是要加到CodeMemberMethod才行的。把那个for 循环算作一句,其实在求Fibonacci数列方法里只有四句语句。下面四句过后,这个Method就算完工了,没它的事了。

                FiboncMethod.Statements.Add(VarF);

                FiboncMethod.Statements.Add(VarF1);

                FiboncMethod.Statements.Add(VarF2);

           FiboncMethod.Statements.Add(forloop);

(VarF2上面出现过了,VarF与VarF1是跟VarF2大同小异的变量声明语句)

 

有了上面的铺垫,讲Main()部分就简单了。CodeEntryPointMethod Start = new CodeEntryPointMethod(); 一句先定义好。下面依旧要从小处着眼,Main()里面的语句一句一句拆开的话全是在上面讲到过的,唯一的提一下的就是if里的Fibonc(N);一句,这句方法调用如果用上面提到过的构造函数也是可以,不过用上面的会产生this.Fibonc(N)这样的语句,视觉效果方面要打点折扣了(因为targetObject这个参数是省不了的,总觉得怪怪的)。其实CodeMethodInvokeExpression还有一个构造函数能派上用场

public CodeMethodInvokeExpression(
   CodeMethodReferenceExpression method,
   params CodeExpression[] parameters
);

new 一个CodeMethodReferneceExpression,直接设置一下MethodName属性,然后用作这里的构造函数参数就行了。不过不这样而用以前的构造函数写法也无伤大雅。

接下来的事是如何把语句组合起来了,首先是看那组if…else语句,产生这种语句的CodeStatement是CodeConditionStatement 看一下它的一个构造函数就真相大白,

public CodeConditionStatement(
   CodeExpression condition,  //条件判断,一般为CodeBinaryOperatorExpression
   CodeStatement[] trueStatements,
   CodeStatement[] falseStatements

);

 

后面两个CodeStatement数组是分别是条件为真与假时程序执行的一些语句,如果程序没有else部分那么第三个参数可省略。

有了上面的经验,异常部分的写法也是迎刃而解了,就是一些CodeStatement数组而已。下面给出的完全的构造函数(try…catch…finally全有)证明了这一点

public CodeTryCatchFinallyStatement(
   CodeStatement[] tryStatements,
   CodeCatchClause[] catchClauses,
   CodeStatement[] finallyStatements

);

(没有finally部分省略第三个参数即可,但如果要没有catch部分的话没有相应的构造函数,只能用无参的构造函数,然后设置tryStatements和finallyStatements属性。CodeDOM里很多类都可以直接用无参的构造函数先new出来,然后再去设置它的相应属性的)

第一与第三个参数似曾相识(就是相识啦),第二个参数第一次看到,而且是个数组(因为异常处理部分可以有多个catch的)。在那个CodeCatchClause里可以设置要捕捉的异常类型,及异常变量名称,当然还有Catch块里的语句

 

好,所有的东西都准备好了,几句Start.Statements.Add(…)结束后这个Main()也没它的事了。

 

最开始的时候说过,所有的东西都是要有归宿的,CodeEntryPointMethod也是CodeMemberMethod的派生类。所以Main()与Fibonc(int n)都是某个Type的Member,要等Type来加噢。

CodeTypeDeclaration MyClass = new CodeTypeDeclaration("DemoClass");

MyClass.Members.Add(Start);

MyClass.Members.Add(FiboncMethod);

后面就是一层一层的往上加(实际到这里后只剩上两层了,Namespace与ComplieUnit)。最后不要忘了导入命名空间就行了,其他的不要,一个System总归是要的

                CodeNamespace sample = new CodeNamespace("Sample");

                sample.Imports.Add(new CodeNamespaceImport("System"));

                sample.Types.Add(MyClass);

 

                CodeCompileUnit compunit = new CodeCompileUnit();

                compunit.Namespaces.Add(sample);

 

上面是从下往上分析的,是为了理解清晰一点。其实写CodeDOM的程序时一般是从上往下写的,而且常常是一个构造函数里套另一个构造函数,一串串的new排在一起。只有一些实在是太长了,或常要用到的,才另定义变量去new 然后用作其他构造函数的参数。而且也不会如上一样把那些Add写在一起,一般都是定义好后就直接Add进去再说,这样也可以在框架打好以后写一段测试一段。