COM

                            先讲一个简单的程序,把变量声明、赋值、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进去再说,这样也可以在框架打好以后写一段测试一段。 
在(三)里用一个求Fibonacci数列的程序来说明CodeDOM是如何生成一些程序的基本语句的。现在写程序很少会直接写几个方法来让Main()从头调到尾的,总是要用几个类来封装封装的。
在CodeDOM里一个类的字段、属性、事件(讲到事件,委托总是逃不了的)、方法等又是如何来生成的呢?上次只讲到了类方法(CodeMemberMethod),在CodeTypeMember这个重量级的类下面还有很多没有涉及,这次用一个比较完整的类来讲余下的部分。

开始部分的几下CodeCompileUnit、CodeNamespace、CodeNamespaceImport都是一样的,就不多说了。
声明一个名为DemoClass的类:
CodeTypeDeclaration MyClass = new CodeTypeDeclaration("DemoClass");
对一个Class来说除了名字外也没什么好设置的,一般情况下这样就行了。但有些定义了一些Attribute的要麻烦一点。要对MyClass的CustomAttributes属性进行设置(不是MyClass的Attributes这个属性,这个是设置可见性的)。
对于Attribute的生成CodeDOM有专门的类来实现,就是CodeAttributeDeclaration。如要对DemoClass这个类加一个[Description("CodeDOM自动生成的一个类")]这样的Attribute可以这样写:
     MyClass.CustomAttributes.Add(new CodeAttributeDeclaration("Description",new CodeAttributeArgument(
       new CodePrimitiveExpression("CodeDOM自动生成的一个类"))));
从上可以看到CodeAttributeDeclaration的构造函数,第一个参数是Attribute的名,第二个参数是Attribute的参数。对Description来说参数是“CodeDOM自动生成的一个类”这么一个字符串。当然不能直接用这个字符串来当构造函数的参数。对于Attribute的参数又有一个专门的类的――CodeAttributeArgument,真是麻烦。还要把字符串包成一个CodeExpression去当CodeAttributeArgument的构造函数的参数才算了结。(最后那个CodePrimitiveExpression在(三)里讲到过了,它返回一个原始的表达式,实际上就是把括号里的东西类型转换成一个CodeExpressoin)。

(上面的长长的一句就是在写CodeDOM程序时构造函数连用的写法。当然你也可以不用这种new串联的写法,可以从最里层开始声明一个个的变量一句句的构造出来。上面一句就是分为四句了,不过这样写变量一多自己也要晕掉,所以一般不是太长的我还是喜欢连写,如果把代码格式编排一下一般写10个new是没什么问题的。比较头痛的是在VS.NET中排好的格式到Word里会变掉)

好,一个类声明好了,下面对类里的元素一个一个来说。
先是字段Field,从它的类层次结构里能看出,这个肯定是用CodeMemberField来做的,如在类里声明
private int myField;
可以这样来写:
     CodeMemberField myField = new CodeMemberField("System.Int32","myField");
     //myField.Attributes = MemberAttributes.Private;
     MyClass.Members.Add(myField);
它的构造函数,前一个参数是类型,后一个参数是字段名。前一个参数有很多种写法,这在(三)里说CodeParameterDeclarationExpression的时候提到过了(构造函数有(Type,string)、(string,string)和(CodeTypeReference,string)这三种写法),在CodeDOM里只要构造函数有关于类型的一般都有上述的Type,string,CodeTypeReference三种写法。
Attribute对于字段来说默认是private。所以这里可以注释掉。
一般地上面几点就够了,如果字段声明的同时要初始化的,只要设一下InitExpression就是了,这跟变量声明不太一样的地方是,它没有相应的构造函数,只能先new出来后,再设置相应的InitExpression属性。下面四句
     CodeMemberField myArray = new CodeMemberField("System.Int32[]","myArray");
     myArray.Attributes = MemberAttributes.Private;
     myArray.InitExpression = new CodeArrayCreateExpression("System.Int32",10);
     MyClass.Members.Add(myArray);
产生private int[] myArray = new int[10];

数组比较的特别一点,声明的时候倒是容易,直接也在类型后加个“[]”就行了,就是初始化的时候麻烦一点,要用到数组创建表达式CodeArrayCreateExpression。它的用法参照一下对应的产生代码就明白了,构造函数都是大同小异的。

字段声明完,接着要写构造函数了。构造函数就是一个比较特殊的类方法,CodeConstructor就是从CodeMemberMethod继承下来声明构造函数用的。除了名字不太一样,用法与CodeMemberMethod及CodeEntryPointMethod差不多,这里就不详述了。

下面是属性的写法:
     CodeMemberProperty MyProperty = new CodeMemberProperty();
     MyProperty.Name = "MyProperty";
     MyProperty.Type = new CodeTypeReference("System.Int32");
     MyProperty.Attributes = MemberAttributes.Public | MemberAttributes.Final;
     MyProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(),"myField")));
     MyProperty.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(),"myField"),
       new CodePropertySetValueReferenceExpression()));
     MyClass.Members.Add(MyProperty);
这么长长的一段出来
        public int MyProperty {
            get {
                return this.myField;
            }
            set {
                this.myField = value;
            }
        }
这个CodeMemberProperty从上往下看,就是依次设置属性名、类型、可见性,以及get和set方法的语句。这个GetStatements.Add()(或SetStatements.Add())里就随你加了,简单的属性设置就如上面一样,复杂的话就不断地依续再往里Add就是了。
下面简单的讲一下Get和Set里一些细节的东西。首先是return this.myField;这个是如何来的,上次讲Fibonacci数列时没讲到,返回值语句就是由CodeMethodReturnStatement产生的,要返回什么构造函数里就放什么,很简单。而this.myField就是类字段了,用CodeFieldReferenceExpression,不是上面的CodeMemberField,那个是声明类字段用的,这里是要引用类字段(跟变量声明与引用差不多)。同理CodePropertyReferenceExpression是就是用到类的属性时用了。在属性设置里“value”这个关键字会频繁的出现,在CodeDOM里对这个“value”也是有个专门的类的,就是CodePropertySetValueReferenceExpression了。比较特别的东西总是有个专门的类来表示,就象“this”就用CodeThisReferenceExpression来表示一样。

在类的属性里有个比较特殊的属性就是索引器属性,设置索引器属性的写法与设置一般的属性基本一样,只是把属性名改为“Item”就行了。如:
public int this[int index] {。。。。。略} 
这么一个索引器属性由下述的语句产生:
     CodeMemberProperty Pindex = new CodeMemberProperty();
     Pindex.Comments.Add(new CodeCommentStatement("索引器属性"));
     Pindex.Type = new CodeTypeReference("System.Int32");
     Pindex.Name = "Item";//属性名设为Item时,CodeDOM就当作是索引器属性处理
     Pindex.Attributes = MemberAttributes.Public | MemberAttributes.Final;
     Pindex.Parameters.Add(new CodeParameterDeclarationExpression("System.Int32","index"));
。。。。下略

类属性的设置是够麻烦的,但最麻烦的要数类事件了,如果只是声明一下事件倒是简单得很,但讲到事件就不得不讲委托,这一下篇幅就长了。这留下次讲。 

上次把一个类的构造函数、字段、属性的写法搞定了,这次轮到事件,讲事件当然就不能不提委托,当然方法也是不少了的(类方法嘛倒是从一开始就在用了,Main()也是类里的方法)。
既然要写一个完整点的东西喽,那就索性再声明一个自己的EventArgs类,这个类基本上什么事没有,只有一个属性返回当前的时间。
    public class myEventArgs : EventArgs {
        
        public myEventArgs() {
        }
        
        public DateTime Dt {
            get {
                return DateTime.Now;
            }
        }
    }
给一下CodeDOM代码,顺便复习一下前面的:
     CodeTypeDeclaration EventArgsClass = new CodeTypeDeclaration("myEventArgs");
     EventArgsClass.BaseTypes.Add("EventArgs");
     //构造函数
     CodeConstructor mEAcreate = new CodeConstructor();
     mEAcreate.Attributes = MemberAttributes.Public;
     EventArgsClass.Members.Add(mEAcreate);
     //属性Dt
     CodeMemberProperty Dt = new CodeMemberProperty();
     Dt.Name = "Dt";
     Dt.Type = new CodeTypeReference("DateTime");
     Dt.Attributes = MemberAttributes.Public | MemberAttributes.Final;
     Dt.GetStatements.Add(new CodeMethodReturnStatement(new CodePropertyReferenceExpression(new 
       CodeTypeReferenceExpression("DateTime"),"Now")));
     EventArgsClass.Members.Add(Dt);

     sample.Types.Add(EventArgsClass);//加到Namespace里

声明委托一句:public delegate void myEventHandler(object sender, myEventArgs e);要用到一个新的CodeDOM类了。上次讲类层次结构的时候没讲到,声明委托用的CodeTypeDelegate是从CodeTypeDeclaration继承下来的,它只是比CodeTypeDeclaration多了两个属性,一个是设置委托的参数:Parameters,一个设置委托的返回类型的ReturnType。
上面的委托没有返回值,至于参数Parameters的用法,与方法里的参数设置是一致的,参数声明就用CodeParameterDeclarationExpression(好象在(三)提到过)。

上面的篇幅是横插进来的,下面回到(四)的那个DemoClass类里,声明一下事件,这倒是很简单。
     CodeMemberEvent MyEvent = new CodeMemberEvent();
     MyEvent.Name = "MyEvent";
     MyEvent.Attributes = MemberAttributes.Public;
     MyEvent.Type = new CodeTypeReference("myEventHandler");
     MyClass.Members.Add(MyEvent);
一眼就能看出来,事件名是“MyEvent”,类型是上面声明过的那个“myEventHandler”
在一个保护方法里引发事件MyEvent:
        // 事件方法
        protected virtual void OnMyEvent(myEventArgs e) {
            if ((this.MyEvent != null)) {
                Console.WriteLine("Invokes MyEvent");
                this.MyEvent(this, e);
            }
        }
随随便便在类里弄个方法写写简单的语句在(三)里就已经熟透了,不过这里曾经大意过一下。this.MyEvent(this, e);这句如果排除上下文及不要去望文生义的话是不是就是一个方法调用啦,第一次写的时候就把它当成方法调用写的,因为方法调用表达CodeMethodInvokeExpression用得是很熟,看到这个形式代码当时想也不想就写成方法调用了。居然编译通过,运行也通过,后来试了一下其实连上面那个this.MyEvent != null 里的“this.Myevent”都可以用CodeFieldReferenceExpression来代替CodeEventReferenceExpression的,出来的代码效果是一样的,但这种写法是概念不清的表现啦,还是改过来。用CodeDelegateInvokeExpression来写this.MyEvent(this, e);这一句:
CodeDelegateInvokeExpression(new CodeEventReferenceExpression(new CodeThisReferenceExpression(),"MyEvent"),new CodeExpression[] {new CodeThisReferenceExpression(),new CodeArgumentReferenceExpression("e")})

到时为了看一下附加委托与移除委托的效果,把OnMyEvent再放到一个public的Method里
        public void MyMethod() {
            Console.WriteLine("DemoClass.MyMethod");
            this.OnMyEvent(new myEventArgs());
        }
这里this.OnMyEvent(new myEventArgs());是的的确确的方法调用了,要用CodeMethodInvokeExpression来做了,不要混了,这个方法里没有什么新东西,就不详述了。(其实还是有的,new myEventArgs()这个就是新东西,不过创建新对象的功能放下次了)
至此事件的功能结束,一个比较完整的类也算完成,当然还有一些功能没有,比如析构函数如何写之类,现在还没找到方法(到时要把CodeDOM中碰到的困惑一起列一下)。
写好东西总是要测试一下的,测试代码也让CodeDOM来生成算了,下次再生成一个测试这个DemoClass的类的代码,主要讲一下对象创建,数组索引(我们上面声明过一个索引器属性的,要用上),及附加、移除委托(让事件发挥作用啦)。 


这么简单的一个类,其实直接让它生成类代码看一下也就知道行不行了。但既然做就做到底,把测试用的代码也一并用CodeDOM来生成算了,而且CodeDOM里还有几个类也要随便说一下。
先看生成后的代码(直接在原namespace里新做了一个类):
public class TestClass {
        
        public static void Main() {
            DemoClass a = new DemoClass();
            DemoClass b = new DemoClass(123);
            Console.WriteLine("MyConst = {0}", DemoClass.MyConst);
            Console.WriteLine("a.MyProperty = {0}", a.MyProperty);
            Console.WriteLine("b.MyProperty = {0}", b.MyProperty);
            a.MyMethod();
            // 附加委托
            a.MyEvent += new myEventHandler(TestClass.MyHandler);
            a.MyMethod();
            // 移除委托
            a.MyEvent -= new myEventHandler(TestClass.MyHandler);
            a.MyMethod();
            a[5] = (a[3] = 11);
            for (int i = 0; (i < 10); i = (i + 1)) {
                Console.WriteLine("a[{0}] = {1}", i, a[i]);
            }
            Console.Read();
        }
        
        static void MyHandler(object sender, myEventArgs e) {
            Console.WriteLine(e.Dt.ToString());
        }
    }

现在虽然看CodeDOM程序时虽能在脑海里浮现生成的相应代码了,但CodeDOM程序还写得不熟,如果不对着生成的代码来写CodeDOM程序还是有点累。现在一般都要先写好目标代码的大致形状,再反过来写CodeDOM程序,现在也照上面生成的代码来分析要写的CodeDOM程序。
CodeTypeDeclaration TestClass = new CodeTypeDeclaration("TestClass");
CodeEntryPointMethod Start = new CodeEntryPointMethod();
开始这两句总归是少了的,下面两句声明变量a、b的只讲一句(第二句稍复杂,就讲它了),变量声明的以前讲到过了。如果不初始化变量倒是简单:
     CodeVariableDeclarationStatement classb = new CodeVariableDeclarationStatement("DemoClass","b")
就OK,后面那个new DemoClass(123)一加就烦了,要设置classb的InitExpression(或者在构造函数里传第三个参数,效果一样的)。一看New什么什么的,下面要用上类的构造函数了,在CodeDOM里创建一个类的新实例的表达式用
public CodeObjectCreateExpression(
   string createType,
   params CodeExpression[] parameters
);
第一个参数是要创建的类型,第二个参数是构造函数的参数,当然没有参数就省略了,上面的CodeDOM程序加上
classb.InitExpression = new CodeObjectCreateExpression("DemoClass",new odePrimitiveExpression(123))
后就完成第二句变量b的声明了。

下面三句输出控制台的语句就是方法调用,这个是很熟的了,一开始就用过的,当然是用CodeMethodInvokeExpression了。它的参数的DemoClass.MyConst与 a.MyProperty这两个分别就是类的字段、属性依次用CodeFieldReferenceExpression、CodePropertyReferenceExpression也很简单的。只看一下第二句的写法:
new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("Console"),"WriteLine",
new CodeExpression[] {new CodePrimitiveExpression("a.MyProperty = {0}"),new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("a"),"MyProperty")});

(我喜欢这种长长的写法,不用去声明一串的变量,这样一眼就能看出来产生的代码是什么。只要输的时候左括号、右括号先对好就不太会出错)

接下来几句是看事件的效果的,先调一下MyMethod,加委托后再调一次,移除委托后最后调一次。主要是附加与移除委托的这两句。
先看new myEventHandler(TestClass.MyHandler)这一句是如何产生的,又是一个new,不过它不是类实例的创建,当然用CodeObjectCreateExpression来写,最后的代码效果是一样的,不过还是不要那样写的好。创建委托用
public CodeDelegateCreateExpression(
   CodeTypeReference delegateType,
   CodeExpression targetObject,
   string methodName
);
所以CodeDelegateCreateExpression delecra = new CodeDelegateCreateExpression(。。。略);
后面的委托new好了,下面就是加上去的事:
CodeAttachEventStatement attevent = new CodeAttachEventStatement(
new CodeVariableReferenceExpression("a"),"MyEvent",delecra);
是附加,那
CodeRemoveEventStatement removevent = new CodeRemoveEventStatement(
new CodeVariableReferenceExpression("a"),"MyEvent",delecra);
就是移除了。

最后看一下类的索引器属性。a[5] = (a[3] = 11);这一句连续赋值的,先看a[5]是如何来的,这实际就是数组里的东西是如何取的问题啦,CodeDOM有一个CodeArrayIndexerExpression刚好可以派上用场。new CodeArrayIndexerExpression(new CodeVariableReferenceExpression("a"),new CodePrimitiveExpression(5))就会产生a[5],很明显前一个参数是索引器的对象,后一个参数是具体的索引数。
下面的连续赋值, CodeDOM生成a[5]=(a[3]=11)这样看起来还是比较的清楚。它就是一个CodeAssignStatement,左项是a[5],而右项(a[3]=11)就是一个运算表达式CodeBinaryOperatorExpression,只不过它的运算符是赋值运算符而已。其实生成(a[5]=a[3])=11也是一样的。
不过上面的这两种写法要写的CodeDOM代码复杂得要命,还不如分成两句赋值语句写来得省事。
后面的一个循环没什么,最简单基本的东西,在(三)里提过了,略过。

下面那个供委托用的“MyHandler”方法只有一句方法调用,也没什么新东西。

用上面的CodeDOM程序生成的代码文件编译产生EXE文件,双击执行,在控制台输出如下:
Instance constructor1
Instance constructor2
MyConst = 12
a.MyProperty = 0
b.MyProperty = 123
DemoClass.MyMethod
DemoClass.MyMethod
Invokes MyEvent
2004-11-04 21:20:50   -------这里当然与程序执行时间有关
DemoClass.MyMethod
a[0] = 0
a[1] = 0
a[2] = 0
a[3] = 11
a[4] = 0
a[5] = 11
a[6] = 0
a[7] = 0
a[8] = 0
a[9] = 0

看来CodeDOM生成的那个类,及后面用来测试它的代码正常的工作。 


写着写着就已经到(七)了,前面讲了那么多,分析如此这般的CodeDOM代码会有什么对应的程序代码;什么样的程序代码CodeDOM程序应该是如何写。其实那都是纸上谈兵的事,一段CodeDOM程序写下来,经过一个个的Add后,最后到了CodeCompileUnit这个CodeDOM容器就停了,没地方可以Add了。就凭这个CodeCompileUnit是不会凭空产生出来我们要的那些代码的喽,CodeDOM只是一个模型而已,只是一个抽象的数据结构。下面的任务就是根据那个CodeDOM生成我们要的源代码(C#、VB或JScript)。
生成与后面的编译源代码用到的是另外一个命名空间----System.CodeDOM.Compiler中的类,这个命名空间比System.CodeDOM那个要瘦多了,主要就是生成与编译时的一些设置,主要的功能都体现在三个接口:ICodeCompiler、ICodeGenerator、ICodeParser。前两个是编译与生成代码用的,第三个是代码语法分析,可以根据具体的代码分析得到CodeDOM,不过目前这个接口是空的,还没有一个类来实现它。

看一下ICodeGenerator这个接口的方法,发现它主要是几个GeneratorCodeFrom*之类的方法,从这可以发现要生成源代码不一定要从CodeCompileUnit来生成。如可以直接从Expression生成代码(GeneratorCodeFromExpression)、可以直接单独生成一个类的代码(GeneratorCodeFromType)等等,它们的用法是大同小异。我们为了得到完整的代码文件就只用GeneratorCodeFromCompileUnit来说事了。
先要得到ICodGenerator这个接口,这个接口要从CodeDomProvider得到(在System.CodeDOM.Compiler中的三个接口都要从CodeDomProvider这里来得到接口),具体如下:
     CSharpCodeProvider provider = new CSharpCodeProvider();
     ICodeGenerator gen = provider.CreateGenerator();
(CSharpCodeProvider是从抽象类CodeDomProvider继承下来的具体类,这里根据要产生的不同源代码选择不同的Provider。如VB就用VBCodeProvider。)
好,得到接口,就可以GeneratorCodeFromCompileUnit了。这个方法的参数
void GenerateCodeFromCompileUnit(
   CodeCompileUnit e,
   TextWriter w,
   CodeGeneratorOptions o
);
第一个参数就是来源了(其他几个From*的方法就是这里跟*FromCompileUnit有点区别而已),第二个参数就是目的地(要输出的代码写到哪去呢?写到这个TextWriter里),一切麻烦的东西都在第三个参数上,不过这个Option倒是比较的简单的,设置的东西也比较的少。
CodeGeneratorOptions里的东西都是一些细枝未节的东西了,一般用默认值就行了。
BlankLinesBetweenMembers:成员之间是否插入空行,默认是true。一般不要动它了,在类成员之间加个空行看起来也舒服点;
BracingStyle:大括号的样式,默认情况下为Block(字符串),左大括号不另起一行;左大括号如果要另起一行就设为C。我一般喜欢把左大括号与它的关联语句放在同一行,这个也不用另设。
ElseOnClosing:说是要不要在每个if或try块结束时追加else、catch或finally块,默认为false。不过我设了一下true,好象不起作用。不知哪里错了。
IndentString:缩进用的字符串。默认为四个空格。这个也不用改了,这样看起来挺舒服的。

所以下面一点代码就是:
     StreamWriter sw = new StreamWriter(Filename,false);
     CodeGeneratorOptions geneop = new CodeGeneratorOptions();
//geneop.BlankLinesBetweenMembers = false;
     //geneop.BracingStyle = "C";
     //geneop.ElseOnClosing = true;
     //geneop.IndentString = "    ";
     gen.GenerateCodeFromCompileUnit(CodeComU,sw,geneop);
     sw.Close();
这之后根据CodeComU这个CodeDOM产生的代码就在Filename这个文件里了。
生成代码真是简单,五、六句就搞定了。 





                                

查看回复