posts - 5, comments - 75, trackbacks - 4, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

2009年4月6日

posted @ 2009-04-06 23:31 Ling Xu 阅读(650) 评论(1) 编辑

2007年11月21日

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

 

posted @ 2007-11-21 16:38 Ling Xu 阅读(2359) 评论(21) 编辑

 

.NET环境下,所有的对象都是通过CLR进行管理,并且由垃圾收集系统来负责回收。我们可以想象得到的是,CLR应当会以某种形式来管理这些对象,并且这些对象与对象之间具有一定的联系。

有一些工具,例如 .NET Memory Profiler等,可以显示出这些关系,但是,可惜的是,这些工具不是免费的。

实际上,Visual Studio .NET本身已经具有这样的功能,只是没有在文档中介绍,并且比上述的工具要复杂一些而已。我们以下面这个小程序来示例如何使用这些功能:

using System;
using System.Collections;

namespace test
{
       class StringHolder
       {
              public string StringData;
              public StringHolder(string stringData)
              {
                     StringData = stringData;
              }
       }
       class TestClass
       {
              public ArrayList Holders;
              [STAThread]
              static void Main(string[] args)
              {
                     TestClass testClass = new TestClass();
                     testClass.Holders = new ArrayList();
                     for (int i = 0; i < 10; ++i)
                     {
                           testClass.Holders.Add(new StringHolder("Hello"));
                     }
                     
                     Console.ReadLine();
              }
       }
}

这个程序的功能还是很简单的,我们在 Console.ReadLine()处设置一个断点,然后按下F5编译并运行程序,Visual Studio .NET会在Console.ReadLine()处暂停。需要注意的是,在编译前需要设定项目属性,在Properties PagesConfiguration Properties条目中的Debugging项中,打开Enable Unmanaged Debugging(设置为True)。

然后我们选择Visual Studio .NETDebug菜单,并且选择Windows->Immediate,这时候会显示一个Command Window – Immediate 窗口。在这个窗口中输入下面文本:

.load sos

这条命令的作用是装入SOS.DLL,这是一个WinDbg的扩展库,用于调试托管代码。

随后输入:

!dumpheap –stat

该命令要求显示程序中所有对象的统计信息,输入该命令后,在Command Window – Immediate窗口中会显示如下信息:

total 61 objects
Statistics:
      MT    Count TotalSize Class Name
  b550ac        1        12 test.TestClass
79c18514        1        20 System.AppDomainSetup
79c20d74        1        24 System.Collections.ArrayList
79c15614        1        32 System.SharedStatics
79c13fc4        2        40 System.Text.StringBuilder
79c14ee4        1        64 System.ExecutionEngineException
79c14dac        1        64 System.StackOverflowException
79c14c74        1        64 System.OutOfMemoryException
79c16e8c        1        80 System.AppDomain
  b723f8        3        92 System.Char[]
 
 b55134       10       120 test.StringHolder
79c125c8       28      1936 System.String
  14dce0        5      6164      Free
  b7209c        5      6328 System.Object[]
Total 61 objects

这些信息包含了很多有用的信息,第一栏,MT表示MethodTable的地址,实际上代表了一种类型的信息,第二栏Count表示该类型的实例数量,第三栏TotalSize表示该类型所有实例总共占用了多少字节的空间,以及最后一栏,Class Name表示了该类型的名字。

因此,我们可以看到,test.TestClass,在整个程序中只有一个实例,并且这个实例占用了12个字节。在我们的程序中有一个循环,创建了10StringHolder,因此在test.StringHolder一行中,注明了test.StringHolder10个实例,总共占用120字节内存。

最后,我们看到有28String,可是在我们的程序中至多只有20个字符串,那么这些字符串又是从哪里来的呢?

我们输入下面这个命令:

!dumpheap -mt 79c125c8

该命令显示MethodTable 79c125c8的详细信息,结果是:

Address       MT     Size
0129117c 79c125c8       28
012911f0 79c125c8      204
012912bc 79c125c8       20
01291308 79c125c8       40
01291330 79c125c8       76
0129137c 79c125c8       32
0129139c 79c125c8       84
012913f0 79c125c8       80
01291440 79c125c8      188
012914fc 79c125c8       36
01291534 79c125c8       52
01291568 79c125c8       32
01291588 79c125c8       32
012915a8 79c125c8       20
012915bc 79c125c8       32
012915dc 79c125c8       64
0129161c 79c125c8       48
01291660 79c125c8       36
01291684 79c125c8       84
012916d8 79c125c8       52
0129170c 79c125c8      108
01291778 79c125c8       88
012917d0 79c125c8       48
01291800 79c125c8      112
01291870 79c125c8       60
012918ac 79c125c8      204
01291978 79c125c8       48
012919b8 79c125c8       28
Bad MethodTable for Obj at 01291ac0
Last good object: 01291ab4
total 28 objects
Statistics:
      MT    Count TotalSize Class Name
79c125c8       28      1936 System.String
Total 28 objects

这个表列出了所有字符串的实例,第一栏是实例的地址,第二栏是实例所属MT的地址,第三栏是实例所占的字节数。我们可以通过下面这条命令来显示一个实例的所属关系:

!gcroot 012919b8

该命令要求调试器查找地址012919b8这个对象实例的信息,输出如下:

Scan Thread 1748 (6d4)
ESP:12f2ec:Root:012919e0(System.Collections.ArrayList)->012919f8(System.Object[])->01291ab4(test.StringHolder)->012919b8(System.String)
ESP:12f318:Root:01291ab4(test.StringHolder)->012919e0(System.Collections.ArrayList)
ESP:12f3b0:Root:01291ab4(test.StringHolder)->012919e0(System.Collections.ArrayList)
ESP:12f458:Root:01291ab4(test.StringHolder)->012919e0(System.Collections.ArrayList)
ESP:12f464:Root:01291ab4(test.StringHolder)->012919e0(System.Collections.ArrayList)
Scan Thread 3364 (d24)
Scan HandleTable 1571f8
Scan HandleTable 14a5b0

从这个结果可以得到我们所希望看到的信息:

Root:012919e0是一个ArrayList,从它连接到012919f8,一个Object数组,然后连接到01291ab4,这是一个StringHolder,最后连接到012919b8,我们所查找的字符串。
然后我们来看一下StringHolder的内容。输入下面命令:

!dumpobj 01291ab4

该命令会显示一个对象的内容:

Name: test.StringHolder
MethodTable 0x00b55134
EEClass 0x00f234ec
Size 12(0xc) bytes
mdToken: 02000002 (C:"Documents and Settings"Andrew"My Documents"Visual Studio Projects"test"bin"Debug"test.exe)
FieldDesc*: 00b550f8
MT Field Offset Type Attr Value Name
00b55134 4000001 4 CLASS instance 012919b8 StringData

上面最后两行构成了一个表格,表示这个对象中所有字段的值。在StringHolder中只有一个字段,是StringData,它的值,指向012919b8,也就是上面看到的连接的字符串。

为了找到ArrayList的所属者,我们继续输入:

!gcroot 012919e0
结果是:

Scan Thread 1748 (6d4)
ESP:12f2ec:Root:012919e0(System.Collections.ArrayList)->012919e0(System.Collections.ArrayList)
ESP:12f310:Root:012919e0(System.Collections.ArrayList)->012919e0(System.Collections.ArrayList)
ESP:12f454:Root:012919e0(System.Collections.ArrayList)->012919e0(System.Collections.ArrayList)
ESP:12f45c:Root:012919d4(test.TestClass)->012919e0(System.Collections.ArrayList)
Scan Thread 3364 (d24)
Scan HandleTable 1571f8
Scan HandleTable 14a5b0


可以看到ArrayList012919d4,也就是TestClass连接。这个对象就是我们定义在Main()中的那个testClass

posted @ 2007-11-21 16:22 Ling Xu 阅读(2024) 评论(18) 编辑

2007年1月16日

摘要: 在.NET中IDisposable接口用于释放资源,但是同时还有Finalize、Close、析构函数等,本文介绍了如何正确实现这些函数。阅读全文

posted @ 2007-01-16 12:44 Ling Xu 阅读(10867) 评论(31) 编辑

2007年1月13日

终于IronPython 1.0正式发布了。这是CPython的.NET移植版本,实现了Python 2.4的绝大部分特性。下载下来看了一下和CPython很相似,不过其纯.NET的血统使得在.NET应用程序中嵌入Python脚本变得相当容易。

在没有IronPython的时候要在.NET程序中使用Python脚本必须要编写CPython的.NET包装器,这是一件比较复杂的事情。不过现在虽然有了IronPython,但是CPython包装器仍旧是有用的。我所看到的原因包括:

1、性能。虽然我还没有测试IronPython的性能,但是按照我的估计,应该和CPython还是有一些差距的;
2、稳定性。IronPython目前只是1.01版,在稳定性上显然不能和已经有近十年历史的CPython相比;
3、功能。CPython是Python的官方版本,目前已经发展到了2.5。如果使用CPython,那么可以随时使用Python的最新特性,而使用IronPython则往往要迟后一个子版本的时间(大约是数个月到一年);
4、扩展库。IronPython的扩展性很好,但是毕竟CPython已经有了很多现有的库可以使用。目前还不清楚IronPython是否兼容于这些库,如果没有兼容的话那么IronPython在目前情况下的库将比CPython少不少。

posted @ 2007-01-13 23:57 Ling Xu 阅读(2235) 评论(8) 编辑