分享
 
 
 

CodeDom:语言的界限在这里消失

王朝other·作者佚名  2008-05-19
窄屏简体版  字體: |||超大  

.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 初始化名称空间

初始化名称空间的代码类似下面这种形式:

private CodeNameSpace InitializeNameSpace(string Name)

{

// 初始化CodeNameSpace变量,指定名称空间的名称

CodeNameSpace CurrentNameSpace = new CodeNamespace (Name);

// 将一些名称空间加入到要导入的名称空间集合。

// 各种语言如何导入名称空间的细节由每种语言对应

// 的CodeProvider分别处理。

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

CurrentNameSpace.Imports.Add (new CodeNamespaceImport("System.Text"));

return CurrentNameSpace;

}

这段代码定义了一个新的名称空间,并导入System和System.Text名称空间。

1.2 创建类

声明一个新类的代码类似下面这种形式:

private CodeTypeDeclaration CreateClass (string Name)

{

// 新建一个CodeTypeDeclaration对象,指定要创建的类的名称

CodeTypeDeclaration ctd = new CodeTypeDeclaration (Name);

// 指定这个CodeType是一个类,而不是一个枚举变量或struct

ctd.IsClass = true;

// 这个类的访问类型是public

ctd.Attributes = MemberAttributes.Public;

// 返回新创建的类

return ctd;

}

CreateClass函数新建一个指定名称的类,做好为该类植入方法、属性、事件的准备。

1.3 创建方法

声明一个新函数的代码类似下面这种形式:

private CodeEntryPointMethod CreateMethod()

{

// 创建一个方法

CodeEntryPointMethod method = new CodeEntryPointMethod();

// 指定该方法的修饰符:public,static

method.Attributes = MemberAttributes.Public |

MemberAttributes.Static;

// 返回新创建的方法

return method;

}

本例创建了一个CodeEntryPointMethod对象。CodeEntryPointMethod对象类似于CodeMemberMethod对象,两者的不同之处在于,CodeProvider会将CodeEntryPointMethod代表的方法作为类的入口点调用,例如作为Sub Main或void main等。对于CodeEntryPointMethod对象,方法的名称默认为Main;对于CodeMemberMethod,方法的名称必须显式指定。

1.4 声明变量

声明一个变量的代码类似下面这种形式:

private CodeVariableDeclarationStatement

DeclareVariables(System.Type DataType,

string Name)

{

// 为将要创建的变量类型创建一个CodeTypeReference对象,

// 这使得我们不必去关注该类数据在特定语言环境中的

// 与数据类型有关的细节问题。

CodeTypeReference tr = new CodeTypeReference (DataType );

// CodeVariableDeclarationStatement对象使得我们不必纠缠于

// 与特定语言有关的下列细节:在该语言的变量声明语句中,

// 应该是数据类型在前,还是变量名称在前;声明变量时是

// 否要用到Dim之类的关键词.

CodeVariableDeclarationStatement Declaration =

new CodeVariableDeclarationStatement(tr, Name);

// CodeObjectCreateExpression负责处理所有调用构造器的细节。

// 大多数情况下应该是new,但有时要使用New。但不管怎样,

// 我们不必去关注这些由语言类型决定的细节.

CodeObjectCreateExpression newStatement = new

CodeObjectCreateExpression ();

// 指定我们要调用其构造器的对象.

newStatement.CreateType = tr;

// 变量将通过调用其构造器的方式初始化.

Declaration.InitExpression = newStatement;

return Declaration;

}

每一种.NET语言都有其特定的数据类型名称,所有这些数据类型都被映射到公共的.NET语言类型。例如,对于C#中称为int的数据类型,在VB.NET中是Integer,公共的.NET类型是System.Int32。CodeTypeReference对象直接使用.NET公共数据类型,以后由每种语言的CodeProvider将它转换成符合各自语言规范的类型名称。

1.5 初始化数组

初始化一个数组的代码类似下面这种形式:

private void InitializeArray (string Name,

params char[] Characters )

{

// 从参数中传入的字符数组获得一个CodeTypeReference 对象,

// 以便在生成的代码中复制该数据类型.

CodeTypeReference tr = new CodeTypeReference (Characters.GetType());

// 声明一个匹配原始数组的数组

CodeVariableDeclarationStatement Declaration =

new CodeVariableDeclarationStatement (tr, Name);

// CodePrimitiveExpression代表“基本”或值数据类型,

// 例如char、int、double等等。

// 我们将用这类基本数据类型构成的一个数组来

// 初始化我们正在声明的数组。

CodePrimitiveExpression[] cpe = new

CodePrimitiveExpression[Characters.Length];

// 循环遍历原始字符数组,

// 为CodePrimitiveExpression类型的数组创建对象。

for (int i = 0; i < Name.Length ; i++)

{

// 每一个CodePrimitiveExpression将有一个字符的语言

// 中立的表示。

cpe[i] = new CodePrimitiveExpression (Characters[i]);

}

// CodeArrayCreateExpression负责调用数组中数据类型的

// 默认构造器。

// 由于我们还传入了一个CodePrimitiveExpression的数组,

// 所以不必指定数组的大小,且数组中的每一个元素都将有

// 合适的初值。

CodeArrayCreateExpression array = new

CodeArrayCreateExpression(tr, cpe);

// 指定:该CodeArrayCreateExpression将初始化数组变量声明。

Declaration.InitExpression = array;

return Declaration;

}

1.6 定义循环结构

声明一个循环结构的代码类似下面这种形式:

private CodeIterationStatement CreateLoop(string LoopControlVariableName)

{

// 声明一个新的变量,该变量将作为

// 循环控制变量

CodeVariableDeclarationStatement Declaration;

// 声明一个管理所有循环逻辑的CodeIterationStatement

CodeIterationStatement forloop = new CodeIterationStatement();

// 为动态声明的变量指定数据类型的另一种方法:

// 用typeof函数获得该数据类型的Type对象,不必

// 用到该类数据的变量

Declaration = new CodeVariableDeclarationStatement(typeof (int),

LoopControlVariableName);

// 指定一个简单的初始化表达式:

// 将新变量设置为0

Declaration.InitExpression = new CodeSnippetExpression ("0");

// 这个新声明的变量将用来初始化循环

forloop.InitStatement = Declaration;

// CodeAssignStatement用来处理赋值语句。

// 这里使用的构造器要求提供两个表达式,第一个位于

// 赋值语句的左边,第二个位于赋值语句的右边。

// 另一种办法是:调用默认的构造器,然后分别显式设置

// 左、右两个表达式。

CodeAssignStatement assignment = new CodeAssignStatement(

new CodeVariableReferenceExpression(LoopControlVariableName),

new CodeSnippetExpression (LoopControlVariableName + " + 1" ));

// 在循环迭代中使用赋值语句。

forloop.IncrementStatement = assignment;

// 当循环控制变量超出数组中的字符个数时,

// 循环结束

forloop.TestExpression = new CodeSnippetExpression

(LoopControlVariableName + " < Characters.Length");

return forloop;

}

注意,这里我们用typeof函数直接获得循环控制变量的数据类型的Type对象,而不是通过声明一个CodeTypeReference对象的方式。这是CodeVariableDeclartionStatement的又一个构造器,实际上其构造器的总数多达7种。

1.7 索引数组

索引一个数组的代码类似下面这种形式:

private CodeArrayIndexerExpression

CreateArrayIndex(string ArrayName, string IndexValue )

{

// 新建一个CodeArrayIndexerExpression

CodeArrayIndexerExpression index = new CodeArrayIndexerExpression ();

// Indices属性是一个能够支持多维数组的集合。不过这里我们只需要

// 一个简单的一维数组。

index.Indices.Add ( new CodeVariableReferenceExpression (IndexValue));

// TargetObject指定了要索引的数组的名称。

index.TargetObject = new CodeSnippetExpression (ArrayName);

return index;

}

CodeArrayIndexerExpression对象处理数组索引方式的种种差异。例如,在C#中数组以ArrayName[IndexValue]的方式索引;但在VB.NET中,数组以ArrayName(IndexValue)的方式索引。

CodeArrayIndexerExpression允许我们忽略这种差异,将注意力集中到其他更重要的问题,例如要索引哪一个数组、要访问第几个数组元素。

二、装配出树结构

我们可以把前面定义的所有函数加入到一个类,通过构造器初始化,例如:

public CodeDomProvider()

{

CurrentNameSpace = InitializeNameSpace("TestSpace");

CodeTypeDeclaration ctd = CreateClass ("HelloWorld");

// 把类加入到名称空间

CurrentNameSpace.Types.Add (ctd);

CodeEntryPointMethod mtd = CreateMethod();

// 把方法加入到类

ctd.Members.Add (mtd);

CodeVariableDeclarationStatement VariableDeclaration =

DeclareVariables (typeof (StringBuilder), "sbMessage");

// 把变量声明加入到方法

mtd.Statements.Add (VariableDeclaration);

CodeVariableDeclarationStatement array = InitializeArray

("Characters", 'H', 'E', 'L', 'L', 'O', ' ',

'W', 'O', 'R', 'L', 'D');

// 把数组加入到方法

mtd.Statements.Add (array);

CodeIterationStatement loop = CreateLoop("intCharacterIndex");

// 把循环加入到方法

mtd.Statements.Add (loop);

// 数组索引

CodeArrayIndexerExpression index = CreateArrayIndex("Characters",

"intCharacterIndex");

// 加入一个语句,它将调用sbMessage对象的“Append”方法

loop.Statements.Add (new CodeMethodInvokeExpression (

new CodeSnippetExpression ("sbMessage"),"Append",

index));

// 循环结束后,输出所有字符追加到sbMessage对象

// 后得到的结果

mtd.Statements.Add (new CodeSnippetExpression

("Console.WriteLine (sbMessage.ToString())"));

}

构造器的运行结果是一个完整的CodeDom树结构。可以看到,至此为止我们的所有操作都独立于目标语言。最后生成的代码将以属性的形式导出。

三、输出生成结果

构造好CodeDom树之后,我们就可以较为方便地将代码以任意.NET语言的形式输出。每一种.NET语言都有相应的CodeProvider对象,CodeProvider对象的CreateGenerator方法能够返回一个实现了ICodeGenerator接口的对象。ICodeGenerator接口定义了用来生成代码的所有方法,而且允许我们定义一个用来简化属性输出的辅助方法。下面的辅助方法GenerateCode负责设置好合适的TextWriter以供输出代码,以字符串的形式返回结果文本。

private string GenerateCode (ICodeGenerator CodeGenerator)

{

// CodeGeneratorOptions允许我们指定各种供代码生成器

// 使用的格式化选项

CodeGeneratorOptions cop = new CodeGeneratorOptions();

// 指定格式:花括号的位置

cop.BracingStyle = "C";

// 指定格式:代码块的缩进方式

cop.IndentString = " ";

// GenerateCodeFromNamespace要求传入一个TextWriter以

// 容纳即将生成的代码。这个TextWriter可以是一个StreamWriter、

// 一个StringWriter或一个IndentedTextWriter。

// StreamWriter可用来将代码输出到文件。

// StringWriter可绑定到StringBuilder,后者可作为一个变量引用。

// 在这里,我们把一个StringWriter绑定到StringBuilder sbCode。

StringBuilder sbCode = new StringBuilder();

StringWriter sw = new StringWriter(sbCode);

// 生成代码!

CodeGenerator.GenerateCodeFromNamespace(CurrentNameSpace, sw,cop);

return sbCode.ToString();

}

有了这个辅助函数,要获取各种语言的代码就相当简单了:

public string VBCode

{

get

{

VBCodeProvider provider = new VBCodeProvider ();

ICodeGenerator codeGen = provider.CreateGenerator ();

return GenerateCode (codeGen);

}

}

public string JScriptCode

{

get

{

JScriptCodeProvider provider = new JScriptCodeProvider ();

ICodeGenerator codeGen = provider.CreateGenerator ();

return GenerateCode(codeGen);

}

}

public string JSharpCode

{

get

{

VJSharpCodeProvider provider = new VJSharpCodeProvider ();

ICodeGenerator codeGen = provider.CreateGenerator ();

return GenerateCode (codeGen);

}

}

public string CSharpCode

{

get

{

CSharpCodeProvider provider = new CSharpCodeProvider();

ICodeGenerator codeGen = provider.CreateGenerator ();

return GeneratorCode (codeGen);

}

}

四、显示出生成的代码

为输出代码,我们要用到一个简单的.aspx文件,它有四个标签,分别对应一种.NET语言:

<table width="800" border="1">

<tr>

<th>VB.NET代码</th>

</tr>

<tr >

<td>

<asp:Label ID="vbCode" Runat="server" CssClass="code">

</asp:Label>

</td>

</tr>

<tr>

<th>

C#代码</th></tr>

<tr>

<td><asp:Label ID="csharpcode" Runat="server" CssClass="code">

</asp:Label></td>

</tr>

<tr>

<th>J#代码</th></tr>

<tr >

<td>

<asp:Label ID="JSharpCode" Runat="server" CssClass="code">

</asp:Label>

</td>

</tr>

<tr>

<th>JScript.NET代码</th>

</tr>

<tr>

<td><asp:Label ID="JScriptCode" Runat="server" CssClass="code">

</asp:Label></td>

</tr>

</table>

在后台执行的代码中,我们实例化一个前面创建的CodeDomProvider类的实例,把它生成的代码赋值给.aspx页面的相应标签的Text属性。为了使Web页面中显示的代码整齐美观,有必要做一些简单的格式化,替换换行符号、空格等,如下所示:

private string FormatCode (string CodeToFormat)

{

string FormattedCode = Regex.Replace (CodeToFormat, "\n", "<br>");

FormattedCode = Regex.Replace (FormattedCode, " " , "");

FormattedCode = Regex.Replace (FormattedCode, ",", ", ");

return FormattedCode;

}

下面把生成的代码显示到Web页面:

private void Page_Load(object sender, System.EventArgs e)

{

HelloWorld.CodeDomProvider codegen = new HelloWorld.CodeDomProvider ();

vbCode.Text = FormatCode (codegen.VBCode);

csharpcode.Text = FormatCode (codegen.CSharpCode);

JScriptCode.Text = FormatCode (codegen.JScriptCode);

JSharpCode.Text = FormatCode (codegen.JSharpCode);

Page.EnableViewState = false;

}

输出结果如下:

VB.NET代码

Imports System

Imports System.Text

Namespace HelloWorld

Public Class Hello_World

Public Shared Sub Main()

Dim sbMessage As System.Text.StringBuilder = _

New System.Text.StringBuilder

Dim Characters() As Char = New Char() {_

Microsoft.VisualBasic.ChrW(72), _

Microsoft.VisualBasic.ChrW(69), _

Microsoft.VisualBasic.ChrW(76), _

Microsoft.VisualBasic.ChrW(76), _

Microsoft.VisualBasic.ChrW(79), _

Microsoft.VisualBasic.ChrW(32), _

Microsoft.VisualBasic.ChrW(87), _

Microsoft.VisualBasic.ChrW(79), _

Microsoft.VisualBasic.ChrW(82), _

Microsoft.VisualBasic.ChrW(76), _

Microsoft.VisualBasic.ChrW(68)}

Dim intCharacterIndex As Integer = 0

Do While intCharacterIndex < Characters.Length

sbMessage.Append(Characters(intCharacterIndex))

intCharacterIndex = intCharacterIndex + 1

Loop

Console.WriteLine (sbMessage.ToString())

End Sub

End Class

End Namespace

C#代码

namespace HelloWorld

{

using System;

using System.Text;

public class Hello_World

{

public static void Main()

{

System.Text.StringBuilder sbMessage = new

System.Text.StringBuilder();

char[] Characters = new char[] {

'H',

'E',

'L',

'L',

'O',

' ',

'W',

'O',

'R',

'L',

'D'};

for (int intCharacterIndex = 0;

intCharacterIndex < Characters.Length;

intCharacterIndex = intCharacterIndex + 1)

{

sbMessage.Append(Characters[intCharacterIndex]);

}

Console.WriteLine (sbMessage.ToString());

}

}

}

J#代码

package HelloWorld;

import System.*;

import System.Text.*;

public class Hello_World

{

public static void main(String[] args)

{

System.Text.StringBuilder sbMessage = new

System.Text.StringBuilder();

char[] Characters = new char[]

{

'H',

'E',

'L',

'L',

'O',

' ',

'W',

'O',

'R',

'L',

'D'}

;

for (int intCharacterIndex = 0;

intCharacterIndex < Characters.Length;

intCharacterIndex = intCharacterIndex + 1)

{

sbMessage.Append(Characters[intCharacterIndex]);

}

Console.WriteLine (sbMessage.ToString());

}

}

JScript.NET代码

//@cc_on

//@set @debug(off)

import System;

import System.Text;

package HelloWorld

{

public class Hello_World

{

public static function Main()

{

var sbMessage : System.Text.StringBuilder =

new System.Text.StringBuilder();

var Characters : char[] =

['H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D'];

for (var intCharacterIndex : int = 0;

; intCharacterIndex < Characters.Length;

intCharacterIndex = intCharacterIndex + 1)

{

sbMessage.Append(Characters[intCharacterIndex]);

}

Console.WriteLine (sbMessage.ToString());

}

}

}

HelloWorld.Hello_World.Main();

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

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有