.NET 环境下运行时代码生成和编译
2007-11-21 16:38
513 查看
CLR自带了各种语言的编译器,例如C#、VB等。通过这些编译器以及反射,可以实现以前在其它环境中做不到的事情:运行时代码生成和编译。
作为一个应用,我们以对象工厂作为示例。对象工厂是通过一些标识符,在运行时生成不同对象的一种设计模式,通常的代码形式为:
public class ObjectFactory
{
public static object CreateInstance(string id)
{
switch (id)
{
case “A”:
return new A();
case “B”:
return new B();
…
default:
return null;
}
}
这段代码非常好,但是存在一个问题是,这是一段源代码,要想在运行时动态增加可以创建的对象就做不到。一种场景就是需要创建的对象是通过配置文件来确定的,那么就不能使用这种方式了。
CLR环境下反射的存在可以非常方便地实现动态工厂,例如:
public class ObjectFactory2
{
public static object CreateInstance(string id)
{
Type type = Type.GetType(id);
return Activator.CreateInstance(type);
}
}
这里要求id是一个CLR类型名称。看上去很漂亮的代码,问题是这段代码非常慢,比前一段代码慢一万倍以上——原因在于在.NET中,几乎所有的反射都会涉及到对虚拟机本身的调用,而这种调用是通过COM进行的。此外还涉及到对整个类型系统的搜索等开销也是很庞大的。
第三种方案,使用一个Hash表以及配合委托进行。在系统初始化的时候创建一个Hash表,表的键为对象标识符,表的值为对象的类型,这样代码就会变成:
public class ObjectFactory3
{
public static Hashtable m_objectTypes;
public static object CreateInstance(string id)
{
Type type = m_objectTypes(id) as Type;
return Activator.CreateInstance(type);
}
}
在这段代码中,节省了Type.GetType的时间,但是并没有回避Activator.CreateInstance的代价,并且在大系统中,使用Hash表的效率也不是很高。
那么有没有更快、但是更加灵活的方式呢?回答是使用动态代码生成和编译技术。
想法是很简单的,我们还是回到第一种方式,switch语句,只要我们在运行时读入需要创建的对象标识符和对象类型,然而按照switch语句的语法创建一个C#源代码文件,然后编译,就可以了。这里涉及到几个问题:
1、 如何书写源代码:这实际上是很简单的,创建一个StringBuilder,然后往里面写字符串就可以了。然而为了提高代码的可读性和方便性,我们可以对其进行一些封装,例如下面这个接口可以完成大部分代码书写的工作。
public interface ICodeWriter
{
void WriteLine();
void WriteLine(int count);
void WriteLine(string s);
void WriteLine(string s, params object[] args);
void Write(string s);
void Write(string s, params object[] args);
void CommentLine();
void CommentLine(int count);
void CommentLine(string s);
void CommentLine(string s, params object[] args);
void Indent();
void Indent(int count);
void UnIndent();
void UnIndent(int count);
void WriteIndents();
}
2、 如何组织源代码:基本上,我们需要下面这些信息:
a) 生成的工厂名称,以及工厂所需要实现的接口;
b) 需要生成的最终对象的类型,在第一个示例中,最终对象的类型是object,然而我们也可以用其它类型来代替,一个比较好的方式是使用一个所有要创建对象的公共基类类型;
c) 对象标识符:这是一个字符串数组,包含所有对象的标识符;
d) 对象类型:这是一个字符串数组或者类型数组,包含所有要创建的对象类型名称或者类型。
有了这些信息以后,我们就可以编写这个工厂的创建程序了:
public class ObjectFactoryBuilder
{
public static string CreateObjectFactorySource(string factoryName, string factoryBaseName, string baseProductName, string [] productIds, string [] productTypes)
{
ICodeWriter writer = new CSharpWriter();
writer.WriteLine("public classs {0}: {1}”, factoryName, factoryBaseName);
writer.Indent();
writer.WriteLine("public {0} CreateInstance(string id)”, baseProductName);
writer.Indent();
writer.WriteLine("switch (name)");
writer.Indent();
for (int k = 0; k< productsIds.Length; ++k)
{
Writer. WriteLine("case \"{0}\": return new {1}();”, productsIds[k], productTypes[k]);
}
Writer.WriteLine("default: return null;");
Writer.Unindent(3);
return Writer.ToString();
}
}
上面这段代码就可以根据传入的信息,自动生成符合C#语法的源代码。
3、 如何编译:在 System.CodeDom.Compiler名字空间中包含了基本的编译器支持,Microsoft.CSharp名字空间中提供了C#编译器的实际对象。首先创建一个CompilerParameter对象,设置编译选项,然后用下面语句创建编译器并且编译代码:
CompilerParameters cp = new CompilerParameters();
// 设置 cp.ReferencedAssemblies
CodeDomProvider provider = new CSharpCodeProvider();
CompileResult cr = provider.CompileAssemblyFromSource(cp, source);
其中,source是一个字符串,包含从CreateObjectFactorySource得到的工厂源代码。如果编译成功,那么cr.Errors.Count == 0,编译生成的配件就是cr.CompiledAssembly。假设我们的工厂名字是ObjectFactory4,实现的接口是IObjectFactory,那么得到配件后,可以写:
Assembly asm = cr.CompiledAssembly;
IObjectFactory factory = asm.CreateInstance("ObjectFactory4") as IObjectFactory;
基于这种工作模式,我们需要在我们自己的配件中定义一个基类或者接口IObjectFactory,然后让动态生成的工厂继承基类或者实现接口,这样我们就可以调用这个工厂来创建对象了。
作为一个应用,我们以对象工厂作为示例。对象工厂是通过一些标识符,在运行时生成不同对象的一种设计模式,通常的代码形式为:
public class ObjectFactory
{
public static object CreateInstance(string id)
{
switch (id)
{
case “A”:
return new A();
case “B”:
return new B();
…
default:
return null;
}
}
这段代码非常好,但是存在一个问题是,这是一段源代码,要想在运行时动态增加可以创建的对象就做不到。一种场景就是需要创建的对象是通过配置文件来确定的,那么就不能使用这种方式了。
CLR环境下反射的存在可以非常方便地实现动态工厂,例如:
public class ObjectFactory2
{
public static object CreateInstance(string id)
{
Type type = Type.GetType(id);
return Activator.CreateInstance(type);
}
}
这里要求id是一个CLR类型名称。看上去很漂亮的代码,问题是这段代码非常慢,比前一段代码慢一万倍以上——原因在于在.NET中,几乎所有的反射都会涉及到对虚拟机本身的调用,而这种调用是通过COM进行的。此外还涉及到对整个类型系统的搜索等开销也是很庞大的。
第三种方案,使用一个Hash表以及配合委托进行。在系统初始化的时候创建一个Hash表,表的键为对象标识符,表的值为对象的类型,这样代码就会变成:
public class ObjectFactory3
{
public static Hashtable m_objectTypes;
public static object CreateInstance(string id)
{
Type type = m_objectTypes(id) as Type;
return Activator.CreateInstance(type);
}
}
在这段代码中,节省了Type.GetType的时间,但是并没有回避Activator.CreateInstance的代价,并且在大系统中,使用Hash表的效率也不是很高。
那么有没有更快、但是更加灵活的方式呢?回答是使用动态代码生成和编译技术。
想法是很简单的,我们还是回到第一种方式,switch语句,只要我们在运行时读入需要创建的对象标识符和对象类型,然而按照switch语句的语法创建一个C#源代码文件,然后编译,就可以了。这里涉及到几个问题:
1、 如何书写源代码:这实际上是很简单的,创建一个StringBuilder,然后往里面写字符串就可以了。然而为了提高代码的可读性和方便性,我们可以对其进行一些封装,例如下面这个接口可以完成大部分代码书写的工作。
public interface ICodeWriter
{
void WriteLine();
void WriteLine(int count);
void WriteLine(string s);
void WriteLine(string s, params object[] args);
void Write(string s);
void Write(string s, params object[] args);
void CommentLine();
void CommentLine(int count);
void CommentLine(string s);
void CommentLine(string s, params object[] args);
void Indent();
void Indent(int count);
void UnIndent();
void UnIndent(int count);
void WriteIndents();
}
2、 如何组织源代码:基本上,我们需要下面这些信息:
a) 生成的工厂名称,以及工厂所需要实现的接口;
b) 需要生成的最终对象的类型,在第一个示例中,最终对象的类型是object,然而我们也可以用其它类型来代替,一个比较好的方式是使用一个所有要创建对象的公共基类类型;
c) 对象标识符:这是一个字符串数组,包含所有对象的标识符;
d) 对象类型:这是一个字符串数组或者类型数组,包含所有要创建的对象类型名称或者类型。
有了这些信息以后,我们就可以编写这个工厂的创建程序了:
public class ObjectFactoryBuilder
{
public static string CreateObjectFactorySource(string factoryName, string factoryBaseName, string baseProductName, string [] productIds, string [] productTypes)
{
ICodeWriter writer = new CSharpWriter();
writer.WriteLine("public classs {0}: {1}”, factoryName, factoryBaseName);
writer.Indent();
writer.WriteLine("public {0} CreateInstance(string id)”, baseProductName);
writer.Indent();
writer.WriteLine("switch (name)");
writer.Indent();
for (int k = 0; k< productsIds.Length; ++k)
{
Writer. WriteLine("case \"{0}\": return new {1}();”, productsIds[k], productTypes[k]);
}
Writer.WriteLine("default: return null;");
Writer.Unindent(3);
return Writer.ToString();
}
}
上面这段代码就可以根据传入的信息,自动生成符合C#语法的源代码。
3、 如何编译:在 System.CodeDom.Compiler名字空间中包含了基本的编译器支持,Microsoft.CSharp名字空间中提供了C#编译器的实际对象。首先创建一个CompilerParameter对象,设置编译选项,然后用下面语句创建编译器并且编译代码:
CompilerParameters cp = new CompilerParameters();
// 设置 cp.ReferencedAssemblies
CodeDomProvider provider = new CSharpCodeProvider();
CompileResult cr = provider.CompileAssemblyFromSource(cp, source);
其中,source是一个字符串,包含从CreateObjectFactorySource得到的工厂源代码。如果编译成功,那么cr.Errors.Count == 0,编译生成的配件就是cr.CompiledAssembly。假设我们的工厂名字是ObjectFactory4,实现的接口是IObjectFactory,那么得到配件后,可以写:
Assembly asm = cr.CompiledAssembly;
IObjectFactory factory = asm.CreateInstance("ObjectFactory4") as IObjectFactory;
基于这种工作模式,我们需要在我们自己的配件中定义一个基类或者接口IObjectFactory,然后让动态生成的工厂继承基类或者实现接口,这样我们就可以调用这个工厂来创建对象了。
相关文章推荐
- .NET 环境下运行时代码生成和编译
- 在Delphi中宿主.NET运行环境,直接调用.NET中的代码
- vs2005 vc++ 生成非托管的 不需要.net运行环境的exe程序方法
- 《UNIX 环境高级编程》编译环境的搭建( 运行本专栏代码必读 )
- vs2005 vc++ 生成非托管的 不需要.net运行环境的exe程序方法
- 经过编译生成的pb程序需要的运行环境
- 在linux 上编译生成windows上运行的exe程序,交叉编译环境的配置( 平台:gentoo linux)
- 公共语言运行时(CLR),它负责管理和执行由.NET 编译器编译产生的中间语言代码
- Android安装NDK运行环境无需cygwin自动编译生成SO
- vc++生成程序不需要.net运行环境的可以执行exe程序的方法
- 把python代码编译成exe文件,及脱离python环境运行py
- Mac 通过命令行编译运行C代码 以及生成和调用静态库 以及Makefile实现过程
- Learning Lua Programming (3) iMac下搭建Lua脚本最好的编码环境(代码补全,编译运行)
- Learning Lua Programming (3) iMac下搭建Lua脚本最好的编码环境(代码补全,编译运行)
- SQL入门前期准备 第一次接触SQL 第一次运行SQL代码 SQL编译环境 SQL新手
- 2015-01-01-windows环境下的.bat文件 快速运行编译用text写的java代码
- 《UNIX 网络编程 第二版》编译环境的搭建( 运行本专栏代码必读 )
- IntelliJ IDEA编译环境编写JSP文件报错且没有代码提示,还能正常运行
- 搭建make环境编译c代码运行在手机中
- Eclipse中对java代码的编译环境和运行环境的设置