细说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,今天的牛皮就吹到这里,希望对各位有帮助。

 
分类: 个人文章
标签: CodeDom表达式语句

CodeDom的更多相关文章

  1. 【.net 深呼吸】细说CodeDom(8):分支与循环

    有人会问,为啥 CodeDom 不会生成 switch 语句,为啥没生成 while 语句之类.要注意,CodeDom只关心代码逻辑,而不是语法,语法是给写代码的人用的.如果用.net的“反编译”工具 ...

  2. 【.net 深呼吸】细说CodeDom(7):索引器

    在开始正题之前,先补充一点前面的内容. 在方法中,如果要引用方法参数,前面的示例中,老周使用的是 CodeVariableReferenceExpression 类,它用于引用变量,也适用于引用方法参 ...

  3. 【.net 深呼吸】细说CodeDom(6):方法参数

    本文老周就给大伙伴们介绍一下方法参数代码的生成. 在开始之前,先补充一下上一篇烂文的内容.在上一篇文章中,老周检讨了 MemberAttributes 枚举的用法,老周此前误以为该枚举不能进行按位操作 ...

  4. 【.net 深呼吸】细说CodeDom(5):类型成员

    前文中,老周已经厚着脸皮介绍了类型的声明,类型里面包含的自然就是类型成员了,故,顺着这个思路,今天咱们就了解一下如何向类型添加成员. 咱们都知道,常见的类型成员,比如字段.属性.方法.事件.表示代码成 ...

  5. 【.net 深呼吸】细说CodeDom(3):命名空间

    在上一篇文章中,老周介绍了表达式和语句,尽管老周没有把所有的内容都讲一遍,但相信大伙至少已经掌握基本用法.在本文中,咱们继续探讨 CodeDom 方面的奥秘,这一次咱们聊聊命名空间. 在开始之前,老周 ...

  6. 【.net 深呼吸】细说CodeDom(2):表达式、语句

    在上一篇文章中,老周厚着脸皮给大伙介绍了代码文档的基本结构,以及一些代码对象与CodeDom类型的对应关系. 在评论中老周看到有朋友提到了 Emit,那老周就顺便提一下.严格上说,Emit并不是针对代 ...

  7. 【.net 深呼吸】细说CodeDom(1):结构大观

    CodeDom 是啥东东?Html Dom听过吧,XML Dom听过吧.DOM一般可翻译为 文档对象模型,那 Code + DOM呢,自然是指代码文档模型了.如果你从来没接触过 CodeDom,你大概 ...

  8. 吐槽CodeDom

    用着.NET Framework,发现了CodeDom的先天性缺陷,心里百般难受. 不知道 CodeDom 是什么的请看这里 CodeDom_百度百科 这里有CodeDom非常全的中文教程 CodeD ...

  9. 这台计算机上缺少此项目引用的 NuGet 程序包-缺少的文件是 ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\build\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props

    异常处理汇总-开发工具  http://www.cnblogs.com/dunitian/p/4522988.html 协助开发里面总有几个是极简爱好者,但是呢删了不该删的就会影响项目开发,下面看下完 ...

  10. 使用CodeDom动态生成类型

    .NET 3.5的时候加入了匿名类型这个特性,我们可以直接使用 new {name="abc"} 来直接生成一个对象.这个特性现在应用的地方很多,比如dapper的查询参数都是用匿 ...

随机推荐

  1. Java 线程池

    系统启动一个线程的成本是比较高的,因为它涉及到与操作系统的交互,使用线程池的好处是提高性能,当系统中包含大量并发的线程时,会导致系统性能剧烈下降,甚至导致JVM崩溃,而线程池的最大线程数参数可以控制系 ...

  2. 运维之Centos apache vsftpd配置

    安装Apache yum install httpd -y chkconfig httpd on service httpd start 配置一下iptables iptables -I INPUT ...

  3. 使用jqgrid的C#/asp.net mvc开发者的福音 jqgrid-asp.net-mvc

    你是否使用jqgrid? 你是否想在C#/asp.net mvc中使用jqgrid? 那你很可能曾经为了分析jqgrid的request url用fiddler忙活了2个小时.(如果你要使用jqgri ...

  4. MySQL Proxy

    最近翻看了mysql proxy的资料,特发上来. mysql proxy的推荐使用方式是采用配置文件来完成,当前在命令行的执行如下:mysql-proxy -P 192.168.1.101:3306 ...

  5. Linux[Fedora]查找文件包含的字段

    find 与 grep组合查找 find . –name '文件类型' | xargs grep –n '查找内容'文件类型可正则表达式通配, [.]表示当前目录下进行查找,也可自由指定目录.比如: ...

  6. 批处理脚本为Mysql重置root密码(重置密码为123456)

    @echo off title mysql ::从注册表找到Mysql的安装路径写入文件mysql.txt reg query HKLM\SYSTEM\ControlSet001\Services\M ...

  7. Makefile变量

    自定义变量 = 是最基本的赋值,会把整个makefile展开之后再决定是多少 x=foo y=$(x)bar #y是asdbar,不是foobar x=asd := 是覆盖之前的值,和=不同,和赋值的 ...

  8. 初识JNI

    需要用到NDK Android 平台从诞生起,就已经支持 C.C++开发.众所周知,Android 的 SDK 基于 Java 实现,这意味着基于 Android SDK 进行开发的第三方应用都必须使 ...

  9. jmeter 如何将上一个请求的结果作为下一个请求的参数——使用正则提取器

    1.简介 Apache JMeter是Apache组织开发的基于Java的压力测试工具.用于对软件做压力测试,它最初被设计用于Web应用测试但后来扩展到其他测试领域. 它可以用于测试静态和动态资源例如 ...

  10. 2-Fedora 17系统安装准备

    在安装Fedora 17系统之前应做好准备工作,这些准备工作包括了解安装系统的硬件要求,Linux系统磁盘分区方案和挂载目录以及制作安装引导盘 学习重点: 硬件要求 硬件兼容性 分区方案 学习内容: ...