.NET 环境下运行时代码生成和编译

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,然后让动态生成的工厂继承基类或者实现接口,这样我们就可以调用这个工厂来创建对象了。

 

转载于:https://www.cnblogs.com/xlshcn/archive/2007/11/21/runtimecodegen.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值