.NET推崇这样一种思想:相对于框架而言,语言处于从属、次要的地位。CodeDom名称空间中包含的类是这一思想的集中体现。我们可以用CodeDom构造一个树或图,用System.CodeDom名称空间的类填充它,完成后,用对应各种.NET语言的CodeProvider对象将树结构转换成该种语言的代码。要更换一种语言,简单到只需更换一下最后用到的CodeProvider对象。
设想一下,利用这一技术,我们至少能够:
·查询存储过程的元数据,构造出一个负责参数绑定的类。
·查询程序集的manifest,构造出一个对每个函数执行单元测试的类。
·为开发组用到的每一种语言生成样板代码。
·只需写一份范例代码,就可以让用户自由选择他们要查看哪一种语言的版本。
·自定义模板语法,经解析后生成任意语言的代码。
·如果要学习某种不熟悉的语言,可以生成该语言的代码,然后将它与熟悉的语言比较。
一、基本操作
System.CodeDom名称空间包含了许多以语言中立的形式描述常见程序结构的对象,每一种语言的细节则由与该种语言对应的CodeProvider对象负责处理。例如,CodeConditionStatement包含一个TrueStatements集合、一个FalseStatements集合和一个条件属性(Condition attribute),但不涉及条件语句块要用“end if”还是右花括号“}”结束,这部分细节由CodeProvider处理。有了这一层抽象,我们就可以描述待生成的代码结构,然后将它以任意语言的形式输出,却不必斤斤计较于各种与特定语言有关的细节问题。同时,这种抽象也为我们通过程序改变代码的结构带来了方便。例如,当我们发现某个方法需要增加一个参数时,就可以将参数加入到该方法的Parameters集合,根本无须改动已生成的代码逻辑。
我们在本文中要用到的大部分对象来自System.CodeDom名称空间,其余对象主要来自各个与特定语言有关的名称空间,例如Microsoft.CSharp名称空间、Microsoft.VisualBasic名称空间、Microsoft.JScript名称空间和Microsoft.VJSharp名称空间。所有这些面向特定语言的名称空间都包含各自的CodeProvider对象。最后,System.CodeDom.Complier名称空间定义ICodeGenerator接口,后者用来把生成的代码输出到一个TextWriter对象。
如果我们只要生成一些用于插件或宏的代码片断,可以利用CodeGenerator从Statement、Expression、Type等生成代码。反之,如果我们要生成的是一个完整的文件,则必须从CodeNameSpace对象入手。在本文的例子中,我们将从一个名称空间开始,示范如何加入import语句、声明类、声明方法、声明变量、实现一个循环结构、索引一个数组,最后,我们将这些技术结合起来,得到一个大家都熟悉的程序。
1.1 初始化名称空间
初始化名称空间的代码类似下面这种形式:
这段代码定义了一个新的名称空间,并导入System和System.Text名称空间。
1.2 创建类
声明一个新类的代码类似下面这种形式:
CreateClass函数新建一个指定名称的类,做好为该类植入方法、属性、事件的准备。
1.3 创建方法
声明一个新函数的代码类似下面这种形式:
本例创建了一个CodeEntryPointMethod对象。CodeEntryPointMethod对象类似于CodeMemberMethod对象,两者的不同之处在于,CodeProvider会将CodeEntryPointMethod代表的方法作为类的入口点调用,例如作为Sub Main或void main等。对于CodeEntryPointMethod对象,方法的名称默认为Main;对于CodeMemberMethod,方法的名称必须显式指定。
1.4 声明变量
声明一个变量的代码类似下面这种形式:
每一种.NET语言都有其特定的数据类型名称,所有这些数据类型都被映射到公共的.NET语言类型。例如,对于C#中称为int的数据类型,在VB.NET中是Integer,公共的.NET类型是System.Int32。CodeTypeReference对象直接使用.NET公共数据类型,以后由每种语言的CodeProvider将它转换成符合各自语言规范的类型名称。
1.5 初始化数组
初始化一个数组的代码类似下面这种形式:
1.6 定义循环结构
声明一个循环结构的代码类似下面这种形式:
注意,这里我们用typeof函数直接获得循环控制变量的数据类型的Type对象,而不是通过声明一个CodeTypeReference对象的方式。这是CodeVariableDeclartionStatement的又一个构造器,实际上其构造器的总数多达7种。
1.7 索引数组
索引一个数组的代码类似下面这种形式:
CodeArrayIndexerExpression对象处理数组索引方式的种种差异。例如,在C#中数组以ArrayName[IndexValue]的方式索引;但在VB.NET中,数组以ArrayName(IndexValue)的方式索引。CodeArrayIndexerExpression允许我们忽略这种差异,将注意力集中到其他更重要的问题,例如要索引哪一个数组、要访问第几个数组元素。
二、装配出树结构
我们可以把前面定义的所有函数加入到一个类,通过构造器初始化,例如:
构造器的运行结果是一个完整的CodeDom树结构。可以看到,至此为止我们的所有操作都独立于目标语言。最后生成的代码将以属性的形式导出。
三、输出生成结果
构造好CodeDom树之后,我们就可以较为方便地将代码以任意.NET语言的形式输出。每一种.NET语言都有相应的CodeProvider对象,CodeProvider对象的CreateGenerator方法能够返回一个实现了ICodeGenerator接口的对象。ICodeGenerator接口定义了用来生成代码的所有方法,而且允许我们定义一个用来简化属性输出的辅助方法。下面的辅助方法GenerateCode负责设置好合适的TextWriter以供输出代码,以字符串的形式返回结果文本。
有了这个辅助函数,要获取各种语言的代码就相当简单了:
四、显示出生成的代码
为输出代码,我们要用到一个简单的.aspx文件,它有四个标签,分别对应一种.NET语言:
在后台执行的代码中,我们实例化一个前面创建的CodeDomProvider类的实例,把它生成的代码赋值给.aspx页面的相应标签的Text属性。为了使Web页面中显示的代码整齐美观,有必要做一些简单的格式化,替换换行符号、空格等,如下所示:
下面把生成的代码显示到Web页面:
总结:CodeDom体现了.NET中语言的重要性不如框架的思想。本文示范了如何运用一些常用的CodeDom类,几乎每一个使用CodeDom技术的应用都要用到这些类。作为一种结构化的代码生成技术,CodeDom有着无限的潜能,唯一的约束恐怕在于人们的想象力。
设想一下,利用这一技术,我们至少能够:
·查询存储过程的元数据,构造出一个负责参数绑定的类。
·查询程序集的manifest,构造出一个对每个函数执行单元测试的类。
·为开发组用到的每一种语言生成样板代码。
·只需写一份范例代码,就可以让用户自由选择他们要查看哪一种语言的版本。
·自定义模板语法,经解析后生成任意语言的代码。
·如果要学习某种不熟悉的语言,可以生成该语言的代码,然后将它与熟悉的语言比较。
一、基本操作
System.CodeDom名称空间包含了许多以语言中立的形式描述常见程序结构的对象,每一种语言的细节则由与该种语言对应的CodeProvider对象负责处理。例如,CodeConditionStatement包含一个TrueStatements集合、一个FalseStatements集合和一个条件属性(Condition attribute),但不涉及条件语句块要用“end if”还是右花括号“}”结束,这部分细节由CodeProvider处理。有了这一层抽象,我们就可以描述待生成的代码结构,然后将它以任意语言的形式输出,却不必斤斤计较于各种与特定语言有关的细节问题。同时,这种抽象也为我们通过程序改变代码的结构带来了方便。例如,当我们发现某个方法需要增加一个参数时,就可以将参数加入到该方法的Parameters集合,根本无须改动已生成的代码逻辑。
我们在本文中要用到的大部分对象来自System.CodeDom名称空间,其余对象主要来自各个与特定语言有关的名称空间,例如Microsoft.CSharp名称空间、Microsoft.VisualBasic名称空间、Microsoft.JScript名称空间和Microsoft.VJSharp名称空间。所有这些面向特定语言的名称空间都包含各自的CodeProvider对象。最后,System.CodeDom.Complier名称空间定义ICodeGenerator接口,后者用来把生成的代码输出到一个TextWriter对象。
如果我们只要生成一些用于插件或宏的代码片断,可以利用CodeGenerator从Statement、Expression、Type等生成代码。反之,如果我们要生成的是一个完整的文件,则必须从CodeNameSpace对象入手。在本文的例子中,我们将从一个名称空间开始,示范如何加入import语句、声明类、声明方法、声明变量、实现一个循环结构、索引一个数组,最后,我们将这些技术结合起来,得到一个大家都熟悉的程序。
1.1 初始化名称空间
初始化名称空间的代码类似下面这种形式:
1
private CodeNameSpace InitializeNameSpace(string Name)
2
{
3
// 初始化CodeNameSpace变量,指定名称空间的名称
4
CodeNameSpace CurrentNameSpace = new CodeNamespace (Name);
5
// 将一些名称空间加入到要导入的名称空间集合。
6
// 各种语言如何导入名称空间的细节由每种语言对应
7
// 的CodeProvider分别处理。
8
CurrentNameSpace.Imports.Add (new CodeNamespaceImport("System"));
9
CurrentNameSpace.Imports.Add (new CodeNamespaceImport("System.Text"));
10
return CurrentNameSpace;
11
}
12
private CodeNameSpace InitializeNameSpace(string Name) 2
{ 3
// 初始化CodeNameSpace变量,指定名称空间的名称 4
CodeNameSpace CurrentNameSpace = new CodeNamespace (Name); 5
// 将一些名称空间加入到要导入的名称空间集合。 6
// 各种语言如何导入名称空间的细节由每种语言对应 7
// 的CodeProvider分别处理。 8
CurrentNameSpace.Imports.Add (new CodeNamespaceImport("System")); 9
CurrentNameSpace.Imports.Add (new CodeNamespaceImport("System.Text")); 10
return CurrentNameSpace; 11
} 12
这段代码定义了一个新的名称空间,并导入System和System.Text名称空间。
1.2 创建类
声明一个新类的代码类似下面这种形式:
1
2
private CodeTypeDeclaration CreateClass (string Name)
3
{
4
// 新建一个CodeTypeDeclaration对象,指定要创建的类的名称
5
CodeTypeDeclaration ctd = new CodeTypeDeclaration (Name);
6
// 指定这个CodeType是一个类,而不是一个枚举变量或struct
7
ctd.IsClass = true;
8
// 这个类的访问类型是public
9
ctd.Attributes = MemberAttributes.Public;
10
// 返回新创建的类
11
return ctd;
12
}
13
2
private CodeTypeDeclaration CreateClass (string Name) 3
{ 4
// 新建一个CodeTypeDeclaration对象,指定要创建的类的名称 5
CodeTypeDeclaration ctd = new CodeTypeDeclaration (Name); 6
// 指定这个CodeType是一个类,而不是一个枚举变量或struct 7
ctd.IsClass = true; 8
// 这个类的访问类型是public 9
ctd.Attributes = MemberAttributes.Public; 10
// 返回新创建的类 11
return ctd; 12
} 13
CreateClass函数新建一个指定名称的类,做好为该类植入方法、属性、事件的准备。
1.3 创建方法
声明一个新函数的代码类似下面这种形式:
1
2
private CodeEntryPointMethod CreateMethod()
3
{
4
// 创建一个方法
5
CodeEntryPointMethod method = new CodeEntryPointMethod();
6
// 指定该方法的修饰符:public,static
7
method.Attributes = MemberAttributes.Public |
8
MemberAttributes.Static;
9
// 返回新创建的方法
10
return method;
11
}
12
2
private CodeEntryPointMethod CreateMethod() 3
{ 4
// 创建一个方法 5
CodeEntryPointMethod method = new CodeEntryPointMethod(); 6
// 指定该方法的修饰符:public,static 7
method.Attributes = MemberAttributes.Public | 8
MemberAttributes.Static; 9
// 返回新创建的方法 10
return method; 11
} 12
本例创建了一个CodeEntryPointMethod对象。CodeEntryPointMethod对象类似于CodeMemberMethod对象,两者的不同之处在于,CodeProvider会将CodeEntryPointMethod代表的方法作为类的入口点调用,例如作为Sub Main或void main等。对于CodeEntryPointMethod对象,方法的名称默认为Main;对于CodeMemberMethod,方法的名称必须显式指定。
1.4 声明变量
声明一个变量的代码类似下面这种形式:
1
private CodeVariableDeclarationStatement
2
DeclareVariables(System.Type DataType,
3
string Name)
4
{
5
// 为将要创建的变量类型创建一个CodeTypeReference对象,
6
// 这使得我们不必去关注该类数据在特定语言环境中的
7
// 与数据类型有关的细节问题。
8
CodeTypeReference tr = new CodeTypeReference (DataType );
9
// CodeVariableDeclarationStatement对象使得我们不必纠缠于
10
// 与特定语言有关的下列细节:在该语言的变量声明语句中,
11
// 应该是数据类型在前,还是变量名称在前;声明变量时是
12
// 否要用到Dim之类的关键词.
13
CodeVariableDeclarationStatement Declaration =
14
new CodeVariableDeclarationStatement(tr, Name);
15
// CodeObjectCreateExpression负责处理所有调用构造器的细节。
16
// 大多数情况下应该是new,但有时要使用New。但不管怎样,
17
// 我们不必去关注这些由语言类型决定的细节.
18
CodeObjectCreateExpression newStatement = new
19
CodeObjectCreateExpression ();
20
// 指定我们要调用其构造器的对象.
21
newStatement.CreateType = tr;
22
// 变量将通过调用其构造器的方式初始化.
23
Declaration.InitExpression = newStatement;
24
return Declaration;
25
}
26
private CodeVariableDeclarationStatement 2
DeclareVariables(System.Type DataType, 3
string Name) 4
{ 5
// 为将要创建的变量类型创建一个CodeTypeReference对象, 6
// 这使得我们不必去关注该类数据在特定语言环境中的 7
// 与数据类型有关的细节问题。 8
CodeTypeReference tr = new CodeTypeReference (DataType ); 9
// CodeVariableDeclarationStatement对象使得我们不必纠缠于 10
// 与特定语言有关的下列细节:在该语言的变量声明语句中, 11
// 应该是数据类型在前,还是变量名称在前;声明变量时是 12
// 否要用到Dim之类的关键词. 13
CodeVariableDeclarationStatement Declaration = 14
new CodeVariableDeclarationStatement(tr, Name); 15
// CodeObjectCreateExpression负责处理所有调用构造器的细节。 16
// 大多数情况下应该是new,但有时要使用New。但不管怎样, 17
// 我们不必去关注这些由语言类型决定的细节. 18
CodeObjectCreateExpression newStatement = new 19
CodeObjectCreateExpression (); 20
// 指定我们要调用其构造器的对象. 21
newStatement.CreateType = tr; 22
// 变量将通过调用其构造器的方式初始化. 23
Declaration.InitExpression = newStatement; 24
return Declaration; 25
} 26
每一种.NET语言都有其特定的数据类型名称,所有这些数据类型都被映射到公共的.NET语言类型。例如,对于C#中称为int的数据类型,在VB.NET中是Integer,公共的.NET类型是System.Int32。CodeTypeReference对象直接使用.NET公共数据类型,以后由每种语言的CodeProvider将它转换成符合各自语言规范的类型名称。
1.5 初始化数组
初始化一个数组的代码类似下面这种形式:
1
private void InitializeArray (string Name,
2
params char[] Characters )
3
{
4
// 从参数中传入的字符数组获得一个CodeTypeReference 对象,
5
// 以便在生成的代码中复制该数据类型.
6
CodeTypeReference tr = new CodeTypeReference (Characters.GetType());
7
// 声明一个匹配原始数组的数组
8
CodeVariableDeclarationStatement Declaration =
9
new CodeVariableDeclarationStatement (tr, Name);
10
// CodePrimitiveExpression代表“基本”或值数据类型,
11
// 例如char、int、double等等。
12
// 我们将用这类基本数据类型构成的一个数组来
13
// 初始化我们正在声明的数组。
14
CodePrimitiveExpression[] cpe = new
15
CodePrimitiveExpression[Characters.Length];
16
// 循环遍历原始字符数组,
17
// 为CodePrimitiveExpression类型的数组创建对象。
18
for (int i = 0; i < Name.Length ; i++)
19
{
20
// 每一个CodePrimitiveExpression将有一个字符的语言
21
// 中立的表示。
22
cpe[i] = new CodePrimitiveExpression (Characters[i]);
23
}
24
// CodeArrayCreateExpression负责调用数组中数据类型的
25
// 默认构造器。
26
// 由于我们还传入了一个CodePrimitiveExpression的数组,
27
// 所以不必指定数组的大小,且数组中的每一个元素都将有
28
// 合适的初值。
29
CodeArrayCreateExpression array = new
30
CodeArrayCreateExpression(tr, cpe);
31
// 指定:该CodeArrayCreateExpression将初始化数组变量声明。
32
Declaration.InitExpression = array;
33
return Declaration;
34
}
35
private void InitializeArray (string Name, 2
params char[] Characters ) 3
{ 4
// 从参数中传入的字符数组获得一个CodeTypeReference 对象, 5
// 以便在生成的代码中复制该数据类型. 6
CodeTypeReference tr = new CodeTypeReference (Characters.GetType()); 7
// 声明一个匹配原始数组的数组 8
CodeVariableDeclarationStatement Declaration = 9
new CodeVariableDeclarationStatement (tr, Name); 10
// CodePrimitiveExpression代表“基本”或值数据类型, 11
// 例如char、int、double等等。 12
// 我们将用这类基本数据类型构成的一个数组来 13
// 初始化我们正在声明的数组。 14
CodePrimitiveExpression[] cpe = new 15
CodePrimitiveExpression[Characters.Length]; 16
// 循环遍历原始字符数组, 17
// 为CodePrimitiveExpression类型的数组创建对象。 18
for (int i = 0; i < Name.Length ; i++) 19
{ 20
// 每一个CodePrimitiveExpression将有一个字符的语言 21
// 中立的表示。 22
cpe[i] = new CodePrimitiveExpression (Characters[i]); 23
} 24
// CodeArrayCreateExpression负责调用数组中数据类型的 25
// 默认构造器。 26
// 由于我们还传入了一个CodePrimitiveExpression的数组, 27
// 所以不必指定数组的大小,且数组中的每一个元素都将有 28
// 合适的初值。 29
CodeArrayCreateExpression array = new 30
CodeArrayCreateExpression(tr, cpe); 31
// 指定:该CodeArrayCreateExpression将初始化数组变量声明。 32
Declaration.InitExpression = array; 33
return Declaration; 34
} 35

1.6 定义循环结构
声明一个循环结构的代码类似下面这种形式:
1
private CodeIterationStatement CreateLoop(string LoopControlVariableName)
2
{
3
// 声明一个新的变量,该变量将作为
4
// 循环控制变量
5
CodeVariableDeclarationStatement Declaration;
6
// 声明一个管理所有循环逻辑的CodeIterationStatement
7
CodeIterationStatement forloop = new CodeIterationStatement();
8
// 为动态声明的变量指定数据类型的另一种方法:
9
// 用typeof函数获得该数据类型的Type对象,不必
10
// 用到该类数据的变量
11
Declaration = new CodeVariableDeclarationStatement(typeof (int),
12
LoopControlVariableName);
13
// 指定一个简单的初始化表达式:
14
// 将新变量设置为0
15
Declaration.InitExpression = new CodeSnippetExpression ("0");
16
// 这个新声明的变量将用来初始化循环
17
forloop.InitStatement = Declaration;
18
// CodeAssignStatement用来处理赋值语句。
19
// 这里使用的构造器要求提供两个表达式,第一个位于
20
// 赋值语句的左边,第二个位于赋值语句的右边。
21
// 另一种办法是:调用默认的构造器,然后分别显式设置
22
// 左、右两个表达式。
23
CodeAssignStatement assignment = new CodeAssignStatement(
24
new CodeVariableReferenceExpression(LoopControlVariableName),
25
new CodeSnippetExpression (LoopControlVariableName + " + 1" ));
26
// 在循环迭代中使用赋值语句。
27
forloop.IncrementStatement = assignment;
28
// 当循环控制变量超出数组中的字符个数时,
29
// 循环结束
30
forloop.TestExpression = new CodeSnippetExpression
31
(LoopControlVariableName + " < Characters.Length");
32
return forloop;
33
}
34
35
private CodeIterationStatement CreateLoop(string LoopControlVariableName) 2
{ 3
// 声明一个新的变量,该变量将作为 4
// 循环控制变量 5
CodeVariableDeclarationStatement Declaration; 6
// 声明一个管理所有循环逻辑的CodeIterationStatement 7
CodeIterationStatement forloop = new CodeIterationStatement(); 8
// 为动态声明的变量指定数据类型的另一种方法: 9
// 用typeof函数获得该数据类型的Type对象,不必 10
// 用到该类数据的变量 11
Declaration = new CodeVariableDeclarationStatement(typeof (int), 12
LoopControlVariableName); 13
// 指定一个简单的初始化表达式: 14
// 将新变量设置为0 15
Declaration.InitExpression = new CodeSnippetExpression ("0"); 16
// 这个新声明的变量将用来初始化循环 17
forloop.InitStatement = Declaration; 18
// CodeAssignStatement用来处理赋值语句。 19
// 这里使用的构造器要求提供两个表达式,第一个位于 20
// 赋值语句的左边,第二个位于赋值语句的右边。 21
// 另一种办法是:调用默认的构造器,然后分别显式设置 22
// 左、右两个表达式。 23
CodeAssignStatement assignment = new CodeAssignStatement( 24
new CodeVariableReferenceExpression(LoopControlVariableName), 25
new CodeSnippetExpression (LoopControlVariableName + " + 1" )); 26
// 在循环迭代中使用赋值语句。 27
forloop.IncrementStatement = assignment; 28
// 当循环控制变量超出数组中的字符个数时, 29
// 循环结束 30
forloop.TestExpression = new CodeSnippetExpression 31
(LoopControlVariableName + " < Characters.Length"); 32
return forloop; 33
} 34
35

注意,这里我们用typeof函数直接获得循环控制变量的数据类型的Type对象,而不是通过声明一个CodeTypeReference对象的方式。这是CodeVariableDeclartionStatement的又一个构造器,实际上其构造器的总数多达7种。
1.7 索引数组
索引一个数组的代码类似下面这种形式:
1
2
private CodeArrayIndexerExpression
3
CreateArrayIndex(string ArrayName, string IndexValue )
4
{
5
// 新建一个CodeArrayIndexerExpression
6
CodeArrayIndexerExpression index = new CodeArrayIndexerExpression ();
7
// Indices属性是一个能够支持多维数组的集合。不过这里我们只需要
8
// 一个简单的一维数组。
9
index.Indices.Add ( new CodeVariableReferenceExpression (IndexValue));
10
// TargetObject指定了要索引的数组的名称。
11
index.TargetObject = new CodeSnippetExpression (ArrayName);
12
return index;
13
}
14
2
private CodeArrayIndexerExpression 3
CreateArrayIndex(string ArrayName, string IndexValue ) 4
{ 5
// 新建一个CodeArrayIndexerExpression 6
CodeArrayIndexerExpression index = new CodeArrayIndexerExpression (); 7
// Indices属性是一个能够支持多维数组的集合。不过这里我们只需要 8
// 一个简单的一维数组。 9
index.Indices.Add ( new CodeVariableReferenceExpression (IndexValue)); 10
// TargetObject指定了要索引的数组的名称。 11
index.TargetObject = new CodeSnippetExpression (ArrayName); 12
return index; 13
} 14
CodeArrayIndexerExpression对象处理数组索引方式的种种差异。例如,在C#中数组以ArrayName[IndexValue]的方式索引;但在VB.NET中,数组以ArrayName(IndexValue)的方式索引。CodeArrayIndexerExpression允许我们忽略这种差异,将注意力集中到其他更重要的问题,例如要索引哪一个数组、要访问第几个数组元素。
二、装配出树结构
我们可以把前面定义的所有函数加入到一个类,通过构造器初始化,例如:
1
public CodeDomProvider()
2
{
3
CurrentNameSpace = InitializeNameSpace("TestSpace");
4
CodeTypeDeclaration ctd = CreateClass ("HelloWorld");
5
// 把类加入到名称空间
6
CurrentNameSpace.Types.Add (ctd);
7
CodeEntryPointMethod mtd = CreateMethod();
8
// 把方法加入到类
9
ctd.Members.Add (mtd);
10
CodeVariableDeclarationStatement VariableDeclaration =
11
DeclareVariables (typeof (StringBuilder), "sbMessage");
12
// 把变量声明加入到方法
13
mtd.Statements.Add (VariableDeclaration);
14
CodeVariableDeclarationStatement array = InitializeArray
15
("Characters", 'H', 'E', 'L', 'L', 'O', ' ',
16
'W', 'O', 'R', 'L', 'D');
17
// 把数组加入到方法
18
mtd.Statements.Add (array);
19
CodeIterationStatement loop = CreateLoop("intCharacterIndex");
20
// 把循环加入到方法
21
mtd.Statements.Add (loop);
22
// 数组索引
23
CodeArrayIndexerExpression index = CreateArrayIndex("Characters",
24
"intCharacterIndex");
25
// 加入一个语句,它将调用sbMessage对象的“Append”方法
26
loop.Statements.Add (new CodeMethodInvokeExpression (
27
new CodeSnippetExpression ("sbMessage"),"Append",
28
index));
29
// 循环结束后,输出所有字符追加到sbMessage对象
30
// 后得到的结果
31
mtd.Statements.Add (new CodeSnippetExpression
32
("Console.WriteLine (sbMessage.ToString())"));
33
}
34
public CodeDomProvider() 2
{ 3
CurrentNameSpace = InitializeNameSpace("TestSpace"); 4
CodeTypeDeclaration ctd = CreateClass ("HelloWorld"); 5
// 把类加入到名称空间 6
CurrentNameSpace.Types.Add (ctd); 7
CodeEntryPointMethod mtd = CreateMethod(); 8
// 把方法加入到类 9
ctd.Members.Add (mtd); 10
CodeVariableDeclarationStatement VariableDeclaration = 11
DeclareVariables (typeof (StringBuilder), "sbMessage"); 12
// 把变量声明加入到方法 13
mtd.Statements.Add (VariableDeclaration); 14
CodeVariableDeclarationStatement array = InitializeArray 15
("Characters", 'H', 'E', 'L', 'L', 'O', ' ', 16
'W', 'O', 'R', 'L', 'D'); 17
// 把数组加入到方法 18
mtd.Statements.Add (array); 19
CodeIterationStatement loop = CreateLoop("intCharacterIndex"); 20
// 把循环加入到方法 21
mtd.Statements.Add (loop); 22
// 数组索引 23
CodeArrayIndexerExpression index = CreateArrayIndex("Characters", 24
"intCharacterIndex"); 25
// 加入一个语句,它将调用sbMessage对象的“Append”方法 26
loop.Statements.Add (new CodeMethodInvokeExpression ( 27
new CodeSnippetExpression ("sbMessage"),"Append", 28
index)); 29
// 循环结束后,输出所有字符追加到sbMessage对象 30
// 后得到的结果 31
mtd.Statements.Add (new CodeSnippetExpression 32
("Console.WriteLine (sbMessage.ToString())")); 33
} 34

构造器的运行结果是一个完整的CodeDom树结构。可以看到,至此为止我们的所有操作都独立于目标语言。最后生成的代码将以属性的形式导出。
三、输出生成结果
构造好CodeDom树之后,我们就可以较为方便地将代码以任意.NET语言的形式输出。每一种.NET语言都有相应的CodeProvider对象,CodeProvider对象的CreateGenerator方法能够返回一个实现了ICodeGenerator接口的对象。ICodeGenerator接口定义了用来生成代码的所有方法,而且允许我们定义一个用来简化属性输出的辅助方法。下面的辅助方法GenerateCode负责设置好合适的TextWriter以供输出代码,以字符串的形式返回结果文本。
1
private string GenerateCode (ICodeGenerator CodeGenerator)
2
{
3
// CodeGeneratorOptions允许我们指定各种供代码生成器
4
// 使用的格式化选项
5
CodeGeneratorOptions cop = new CodeGeneratorOptions();
6
// 指定格式:花括号的位置
7
cop.BracingStyle = "C";
8
// 指定格式:代码块的缩进方式
9
cop.IndentString = " ";
10
// GenerateCodeFromNamespace要求传入一个TextWriter以
11
// 容纳即将生成的代码。这个TextWriter可以是一个StreamWriter、
12
// 一个StringWriter或一个IndentedTextWriter。
13
// StreamWriter可用来将代码输出到文件。
14
// StringWriter可绑定到StringBuilder,后者可作为一个变量引用。
15
// 在这里,我们把一个StringWriter绑定到StringBuilder sbCode。
16
StringBuilder sbCode = new StringBuilder();
17
StringWriter sw = new StringWriter(sbCode);
18
19
// 生成代码!
20
CodeGenerator.GenerateCodeFromNamespace(CurrentNameSpace, sw,cop);
21
return sbCode.ToString();
22
}
23
private string GenerateCode (ICodeGenerator CodeGenerator) 2
{ 3
// CodeGeneratorOptions允许我们指定各种供代码生成器 4
// 使用的格式化选项 5
CodeGeneratorOptions cop = new CodeGeneratorOptions(); 6
// 指定格式:花括号的位置 7
cop.BracingStyle = "C"; 8
// 指定格式:代码块的缩进方式 9
cop.IndentString = " "; 10
// GenerateCodeFromNamespace要求传入一个TextWriter以 11
// 容纳即将生成的代码。这个TextWriter可以是一个StreamWriter、 12
// 一个StringWriter或一个IndentedTextWriter。 13
// StreamWriter可用来将代码输出到文件。 14
// StringWriter可绑定到StringBuilder,后者可作为一个变量引用。 15
// 在这里,我们把一个StringWriter绑定到StringBuilder sbCode。 16
StringBuilder sbCode = new StringBuilder(); 17
StringWriter sw = new StringWriter(sbCode); 18
19
// 生成代码! 20
CodeGenerator.GenerateCodeFromNamespace(CurrentNameSpace, sw,cop); 21
return sbCode.ToString(); 22
} 23

有了这个辅助函数,要获取各种语言的代码就相当简单了:
1
public string VBCode
2
{
3
get
4
{
5
VBCodeProvider provider = new VBCodeProvider ();
6
ICodeGenerator codeGen = provider.CreateGenerator ();
7
return GenerateCode (codeGen);
8
}
9
10
}
11
12
public string JScriptCode
13
{
14
get
15
{
16
JScriptCodeProvider provider = new JScriptCodeProvider ();
17
ICodeGenerator codeGen = provider.CreateGenerator ();
18
return GenerateCode(codeGen);
19
}
20
21
}
22
23
public string JSharpCode
24
{
25
get
26
{
27
VJSharpCodeProvider provider = new VJSharpCodeProvider ();
28
ICodeGenerator codeGen = provider.CreateGenerator ();
29
return GenerateCode (codeGen);
30
}
31
32
}
33
34
public string CSharpCode
35
{
36
get
37
{
38
CSharpCodeProvider provider = new CSharpCodeProvider();
39
ICodeGenerator codeGen = provider.CreateGenerator ();
40
return GeneratorCode (codeGen);
41
}
42
43
}
44
public string VBCode 2
{ 3
get 4
{ 5
VBCodeProvider provider = new VBCodeProvider (); 6
ICodeGenerator codeGen = provider.CreateGenerator (); 7
return GenerateCode (codeGen); 8
} 9
10
} 11
12
public string JScriptCode 13
{ 14
get 15
{ 16
JScriptCodeProvider provider = new JScriptCodeProvider (); 17
ICodeGenerator codeGen = provider.CreateGenerator (); 18
return GenerateCode(codeGen); 19
} 20
21
} 22
23
public string JSharpCode 24
{ 25
get 26
{ 27
VJSharpCodeProvider provider = new VJSharpCodeProvider (); 28
ICodeGenerator codeGen = provider.CreateGenerator (); 29
return GenerateCode (codeGen); 30
} 31
32
} 33
34
public string CSharpCode 35
{ 36
get 37
{ 38
CSharpCodeProvider provider = new CSharpCodeProvider(); 39
ICodeGenerator codeGen = provider.CreateGenerator (); 40
return GeneratorCode (codeGen); 41
} 42
43
} 44

四、显示出生成的代码
为输出代码,我们要用到一个简单的.aspx文件,它有四个标签,分别对应一种.NET语言:
1
<table width="800" border="1">
2
<tr>
3
<th>VB.NET代码</th>
4
</tr>
5
<tr >
6
<td>
7
<asp:Label ID="vbCode" Runat="server" CssClass="code">
8
</asp:Label>
9
</td>
10
</tr>
11
<tr>
12
<th>
13
C#代码</th></tr>
14
<tr>
15
<td><asp:Label ID="csharpcode" Runat="server" CssClass="code">
16
</asp:Label></td>
17
</tr>
18
<tr>
19
<th>J#代码</th></tr>
20
<tr >
21
<td>
22
<asp:Label ID="JSharpCode" Runat="server" CssClass="code">
23
</asp:Label>
24
</td>
25
</tr>
26
<tr>
27
<th>JScript.NET代码</th>
28
</tr>
29
<tr>
30
<td><asp:Label ID="JScriptCode" Runat="server" CssClass="code">
31
</asp:Label></td>
32
</tr>
33
</table>
34
<table width="800" border="1"> 2
<tr> 3
<th>VB.NET代码</th> 4
</tr> 5
<tr > 6
<td> 7
<asp:Label ID="vbCode" Runat="server" CssClass="code"> 8
</asp:Label> 9
</td> 10
</tr> 11
<tr> 12
<th> 13
C#代码</th></tr> 14
<tr> 15
<td><asp:Label ID="csharpcode" Runat="server" CssClass="code"> 16
</asp:Label></td> 17
</tr> 18
<tr> 19
<th>J#代码</th></tr> 20
<tr > 21
<td> 22
<asp:Label ID="JSharpCode" Runat="server" CssClass="code"> 23
</asp:Label> 24
</td> 25
</tr> 26
<tr> 27
<th>JScript.NET代码</th> 28
</tr> 29
<tr> 30
<td><asp:Label ID="JScriptCode" Runat="server" CssClass="code"> 31
</asp:Label></td> 32
</tr> 33
</table> 34

在后台执行的代码中,我们实例化一个前面创建的CodeDomProvider类的实例,把它生成的代码赋值给.aspx页面的相应标签的Text属性。为了使Web页面中显示的代码整齐美观,有必要做一些简单的格式化,替换换行符号、空格等,如下所示:
1
2
private string FormatCode (string CodeToFormat)
3
{
4
string FormattedCode = Regex.Replace (CodeToFormat, "\n", "<br>");
5
FormattedCode = Regex.Replace (FormattedCode, " " , " ");
6
FormattedCode = Regex.Replace (FormattedCode, ",", ", ");
7
return FormattedCode;
8
}
9
2
private string FormatCode (string CodeToFormat) 3
{ 4
string FormattedCode = Regex.Replace (CodeToFormat, "\n", "<br>"); 5
FormattedCode = Regex.Replace (FormattedCode, " " , " "); 6
FormattedCode = Regex.Replace (FormattedCode, ",", ", "); 7
return FormattedCode; 8
} 9
下面把生成的代码显示到Web页面:
1
2
private void Page_Load(object sender, System.EventArgs e)
3
{
4
5
HelloWorld.CodeDomProvider codegen = new HelloWorld.CodeDomProvider ();
6
vbCode.Text = FormatCode (codegen.VBCode);
7
csharpcode.Text = FormatCode (codegen.CSharpCode);
8
JScriptCode.Text = FormatCode (codegen.JScriptCode);
9
JSharpCode.Text = FormatCode (codegen.JSharpCode);
10
Page.EnableViewState = false;
11
}
12
13
14
15
输出结果如下:
16
17
VB.NET代码
18
19
Imports System
20
Imports System.Text
21
22
Namespace HelloWorld
23
24
Public Class Hello_World
25
26
Public Shared Sub Main()
27
Dim sbMessage As System.Text.StringBuilder = _
28
New System.Text.StringBuilder
29
Dim Characters() As Char = New Char() {_
30
Microsoft.VisualBasic.ChrW(72), _
31
Microsoft.VisualBasic.ChrW(69), _
32
Microsoft.VisualBasic.ChrW(76), _
33
Microsoft.VisualBasic.ChrW(76), _
34
Microsoft.VisualBasic.ChrW(79), _
35
Microsoft.VisualBasic.ChrW(32), _
36
Microsoft.VisualBasic.ChrW(87), _
37
Microsoft.VisualBasic.ChrW(79), _
38
Microsoft.VisualBasic.ChrW(82), _
39
Microsoft.VisualBasic.ChrW(76), _
40
Microsoft.VisualBasic.ChrW(68)}
41
Dim intCharacterIndex As Integer = 0
42
Do While intCharacterIndex < Characters.Length
43
sbMessage.Append(Characters(intCharacterIndex))
44
intCharacterIndex = intCharacterIndex + 1
45
Loop
46
Console.WriteLine (sbMessage.ToString())
47
End Sub
48
End Class
49
End Namespace
50
51
C#代码
52
53
namespace HelloWorld
54
{
55
using System;
56
using System.Text;
57
58
public class Hello_World
59
{
60
public static void Main()
61
{
62
System.Text.StringBuilder sbMessage = new
63
System.Text.StringBuilder();
64
char[] Characters = new char[] {
65
'H',
66
'E',
67
'L',
68
'L',
69
'O',
70
' ',
71
'W',
72
'O',
73
'R',
74
'L',
75
'D'};
76
for (int intCharacterIndex = 0;
77
intCharacterIndex < Characters.Length;
78
intCharacterIndex = intCharacterIndex + 1)
79
{
80
sbMessage.Append(Characters[intCharacterIndex]);
81
}
82
Console.WriteLine (sbMessage.ToString());
83
}
84
}
85
}
86
87
J#代码
88
89
package HelloWorld;
90
import System.*;
91
import System.Text.*;
92
93
94
public class Hello_World
95
{
96
public static void main(String[] args)
97
{
98
System.Text.StringBuilder sbMessage = new
99
System.Text.StringBuilder();
100
char[] Characters = new char[]
101
{
102
'H',
103
'E',
104
'L',
105
'L',
106
'O',
107
' ',
108
'W',
109
'O',
110
'R',
111
'L',
112
'D'}
113
;
114
for (int intCharacterIndex = 0;
115
intCharacterIndex < Characters.Length;
116
intCharacterIndex = intCharacterIndex + 1)
117
{
118
sbMessage.Append(Characters[intCharacterIndex]);
119
}
120
Console.WriteLine (sbMessage.ToString());
121
}
122
}
123
124
125
JScript.NET代码
126
127
128
//@cc_on
129
//@set @debug(off)
130
131
import System;
132
import System.Text;
133
134
package HelloWorld
135
{
136
137
public class Hello_World
138
{
139
140
public static function Main()
141
{
142
var sbMessage : System.Text.StringBuilder =
143
new System.Text.StringBuilder();
144
var Characters : char[] =
145
['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D'];
146
for (var intCharacterIndex : int = 0;
147
; intCharacterIndex < Characters.Length;
148
intCharacterIndex = intCharacterIndex + 1)
149
{
150
sbMessage.Append(Characters[intCharacterIndex]);
151
}
152
Console.WriteLine (sbMessage.ToString());
153
}
154
}
155
}
156
HelloWorld.Hello_World.Main();
157

2
private void Page_Load(object sender, System.EventArgs e) 3
{ 4
5
HelloWorld.CodeDomProvider codegen = new HelloWorld.CodeDomProvider (); 6
vbCode.Text = FormatCode (codegen.VBCode); 7
csharpcode.Text = FormatCode (codegen.CSharpCode); 8
JScriptCode.Text = FormatCode (codegen.JScriptCode); 9
JSharpCode.Text = FormatCode (codegen.JSharpCode); 10
Page.EnableViewState = false; 11
} 12
13
14
15
输出结果如下: 16
17
VB.NET代码 18
19
Imports System 20
Imports System.Text 21
22
Namespace HelloWorld 23
24
Public Class Hello_World 25
26
Public Shared Sub Main() 27
Dim sbMessage As System.Text.StringBuilder = _ 28
New System.Text.StringBuilder 29
Dim Characters() As Char = New Char() {_ 30
Microsoft.VisualBasic.ChrW(72), _ 31
Microsoft.VisualBasic.ChrW(69), _ 32
Microsoft.VisualBasic.ChrW(76), _ 33
Microsoft.VisualBasic.ChrW(76), _ 34
Microsoft.VisualBasic.ChrW(79), _ 35
Microsoft.VisualBasic.ChrW(32), _ 36
Microsoft.VisualBasic.ChrW(87), _ 37
Microsoft.VisualBasic.ChrW(79), _ 38
Microsoft.VisualBasic.ChrW(82), _ 39
Microsoft.VisualBasic.ChrW(76), _ 40
Microsoft.VisualBasic.ChrW(68)} 41
Dim intCharacterIndex As Integer = 0 42
Do While intCharacterIndex < Characters.Length 43
sbMessage.Append(Characters(intCharacterIndex)) 44
intCharacterIndex = intCharacterIndex + 1 45
Loop 46
Console.WriteLine (sbMessage.ToString()) 47
End Sub 48
End Class 49
End Namespace 50
51
C#代码 52
53
namespace HelloWorld 54
{ 55
using System; 56
using System.Text; 57
58
public class Hello_World 59
{ 60
public static void Main() 61
{ 62
System.Text.StringBuilder sbMessage = new 63
System.Text.StringBuilder(); 64
char[] Characters = new char[] { 65
'H', 66
'E', 67
'L', 68
'L', 69
'O', 70
' ', 71
'W', 72
'O', 73
'R', 74
'L', 75
'D'}; 76
for (int intCharacterIndex = 0; 77
intCharacterIndex < Characters.Length; 78
intCharacterIndex = intCharacterIndex + 1) 79
{ 80
sbMessage.Append(Characters[intCharacterIndex]); 81
} 82
Console.WriteLine (sbMessage.ToString()); 83
} 84
} 85
} 86
87
J#代码 88
89
package HelloWorld; 90
import System.*; 91
import System.Text.*; 92
93
94
public class Hello_World 95
{ 96
public static void main(String[] args) 97
{ 98
System.Text.StringBuilder sbMessage = new 99
System.Text.StringBuilder(); 100
char[] Characters = new char[] 101
{ 102
'H', 103
'E', 104
'L', 105
'L', 106
'O', 107
' ', 108
'W', 109
'O', 110
'R', 111
'L', 112
'D'} 113
; 114
for (int intCharacterIndex = 0; 115
intCharacterIndex < Characters.Length; 116
intCharacterIndex = intCharacterIndex + 1) 117
{ 118
sbMessage.Append(Characters[intCharacterIndex]); 119
} 120
Console.WriteLine (sbMessage.ToString()); 121
} 122
} 123
124
125
JScript.NET代码 126
127
128
//@cc_on 129
//@set @debug(off) 130
131
import System; 132
import System.Text; 133
134
package HelloWorld 135
{ 136
137
public class Hello_World 138
{ 139
140
public static function Main() 141
{ 142
var sbMessage : System.Text.StringBuilder = 143
new System.Text.StringBuilder(); 144
var Characters : char[] = 145
['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D']; 146
for (var intCharacterIndex : int = 0; 147
; intCharacterIndex < Characters.Length; 148
intCharacterIndex = intCharacterIndex + 1) 149
{ 150
sbMessage.Append(Characters[intCharacterIndex]); 151
} 152
Console.WriteLine (sbMessage.ToString()); 153
} 154
} 155
} 156
HelloWorld.Hello_World.Main(); 157

总结:CodeDom体现了.NET中语言的重要性不如框架的思想。本文示范了如何运用一些常用的CodeDom类,几乎每一个使用CodeDom技术的应用都要用到这些类。作为一种结构化的代码生成技术,CodeDom有着无限的潜能,唯一的约束恐怕在于人们的想象力。

