拓展动态编程的新领域

📅 2026/7/5 2:16:00
拓展动态编程的新领域
很久没有发文了贴一篇新文吧。从Word直接贴过来的没仔细排版诸位海涵。有关DLR和C# 4动态特性的详细介绍请参看本人拙著《.NET 4.0面向对象编程揭秘应用篇》目前该书正处于编辑出版流程中估计12月上市。与此书相关的技术资源将陆续发布于博客园与CSDN的本人博客。金旭亮近几年来在TIOBE公司每个月发布的编程语言排行榜[1]中C#总是能挤进前10名而在近10年的编程语言排行榜中C#总体上呈现上升的趋势。C#能取得这样的成绩有很多因素在起作用其中它在语言特性上的锐意进取让人印象深刻图 1。图 1 C#各版本的创新点2010年发布的C# 4最大的创新点是拥有了动态编程语言的特性。1 动态编程语言的中兴动态编程语言并非什么新鲜事物早在面向对象编程语言成为主流之前人们就已经使用动态编程语言来开发了。即使在Java、C#、C等面向对象编程语言繁荣兴旺、大行于世的年代动态编程语言也在“悄悄”地攻城掠地占据了相当的开发领域比如 JavaScript业已成为Web客户端事实上的主流语言。最近这几年动态编程语言变得日益流行比如Python、Ruby都非常活跃使用者众多。这里有一个问题为什么我们需要在开发中应用动态编程语言与C#和Java这类已经非常成熟且功能强大的静态类型编程语言相比动态编程语言有何优势简单地说使用动态编程语言开发拥有以下的特性1支持REPLRead-evaluate-print Loop“读入à执行à输出”循环迭代的开发模式整个过程简洁明了直指问题的核心。举个简单的例子图 2所示为使用IronPython[2]编程计算“12……100”的屏幕截图我们可以快速地输入一段完成累加求和的代码然后马上就可以看到结果图 2 使用IronPython编程如果使用C#开发就麻烦多了您得先用Visual Studio创建一个项目然后向其中添加一个类在类中写一个方法完成求和的功能再编写调用这一方法的代码编译、排错最后才能得到所需的结果……很明显对于那些短小的工作任务而言动态编程语言所具备的这种REPL开发模式具有很大的吸引力。2扩展方便。用户可以随时对代码进行调整需要什么功能直接往动态对象上“加”就是了不要时又可以移除它们。而且这种修改可以马上生效并不需要像C#那样必须先修改类型的定义和声明编译之后新方法才可用。换句话说使用动态语言编程不需要“重量级”的OOAD整个开发过程迭代迅速而从不拖泥带水。3动态编程语言的类型解析是在运行时完成的可以省去许多不必要的类型转换代码因此与静态编程语相比动态编程语言写的代码往往更紧凑量更少。动态编程语言主要的弱点有两个1代码中的许多错误要等到运行时才能发现而且需要特定的运行环境支持对其进行测试不太方便也不支持许多用于提升代码质量的各种软件工程工具因此不太适合于开发规模较大的、包容复杂处理逻辑的应用系统。2与静态编程语言相比动态编程语言编写的程序性能较低。不过随着计算机软硬件技术的不断进步比如多核CPU的广泛应用动态编程语言引擎和运行环境不断地优化动态编程语言编写的程序性能在不断地提升在特定的应用场景下甚至可以逼近静态语言编写的程序。2 拥抱“动态编程”特性的C# 4为了让C#、Visual Basic等.NET编程语言能具备动态编程语言的特性.NET 4.0引入了一个“DLRDynamic Language Runtime动态语言运行时”图 3。图 3 DLR动态语言运行时DLR运行于CLR之上提供了一个动态语言的运行环境从而允许Python、Ruby等动态语言编写的程序在.NET平台上运行同时现有的.NET静态类型编程语言比如C#和Visual Basic也可以利用DLR而拥有一些动态编程语言的特性。1使用C# 4编写动态的代码C# 4新增了一个dynamic关键字可以用它来编写“动态”的代码。例如以下代码创建了一个ExpandoObject对象注意必须定义为dynamicdynamic dynamicObj new ExpandoObject();这一对象的奇特之处在于我们可以随时给它增加新成员dynamicObj.Value 100; //添加字段dynamicObj.Increment new Action(() dynamicObj.Value); //添加方法这些动态添加的成员与普通的类成员用法一样for (int i 0; i 10; i)dynamicObj.Increment();//调用方法Console.WriteLine(dynamicObj.Value{0},dynamicObj.Value);//访问字段ExpandoObject对象实现了IDictionarystring, object接口可看成是一个字典对象所有动态添加的成员都是这个字典对象中的元素这意味我们不仅可以添加新成员还可以随时移除不再需要的成员//移除Increment方法(dynamicObj as IDictionarystring, object).Remove(Increment);方法移除之后再尝试访问此方法将引发RuntimeBinderException异常。2使用dynamic关键字简化与COM组件交互的代码要在.NET这个“托管世界”里调用“非托管世界”中的COM组件我们必须通过 “互操作程序集Interop Assembly”作为桥梁“互操作程序集”定义了CLR类型与COM类型之间的对应关系。只要给.NET项目添加对“互操作程序集”的引用就可以在.NET应用程序中创建这一程序集所包容的各种类型的实例即COM包装器对象对这些对象的方法调用或对其属性的存取将会被转发给COM组件。以调用Word为例在C# 4.0之前您可能经常需要编写这样的代码Object wordapp new Word.Application(); //创建Word对象Object fileName “MyDoc.docx” ;//指定Word文档Object argu System.Reflection.Missing.Value;Word.Document doc wordapp.Documents.Open(ref fileName, ref argu,ref argu, ref argu, ref argu, ref argu, ref argu, ref argu,ref argu, ref argu, ref argu, ref argu, ref argu, ref argu,ref argu, ref argu);上述对Open()方法的调用语句只能用“恐怖”一词来形容其原因是Word组件中的Open()方法定义了太多的参数。C#4使用dynamic关键字配合从Visual Basic中学来的“命名参数与可选参数”这两个新语法特性可以写出更简洁的代码dynamic wordapp new Word.Application();dynamic doc wordapp.Documents.Open(FileName: “MyDoc.docx”);上述代码中省去了用不着的参数并且可以去掉参数前的ref关键字。当上述代码运行时DLR会使用反射技术将dynamic表达式“绑定bind”到COM互操作程序集中所包容的Word.Application代理对象。3C# 4动态编程技术内幕C#4中所定义的dynamic变量可以引用以下类型的对象l 传统的“静态”的CLR对象。l COM包装器对象。前面已经介绍了这方面的内容。l 实现了IDynamicMetaObjectProvider接口的“动态对象”ExpandoObject就是这种类型对象的实例。l 基于DLR实现的动态语言比如IronRuby和IronPython所创建的对象。从C#程序员角度来看所有这四种对象都是一样的都可用一个dynamic变量引用之而DLR在程序运行时动态地将方法调用和字段存取请求“绑定”到真正的对象上。dynamic的功能是由DLR所支撑的是C#编译器与DLR分工合作的成果。请看以下示例代码dynamic d 100;d;C#编译器在处理上述代码时它并不去检查变量d是否可以支持自增操作而是为其创建了一个CallSiteT对象p__Site1private static class Maino__SiteContainer0 {public static CallSiteFuncCallSite, object, object p__Site1;}中文MSDN将CallSiteT译为“动态调用站点”它是DLR中的核心组件之一。动态站点对象通过CallSiteT.Create()方法创建 C#编译器会为其指定一个派生自CallSiteBinder的对象称为“动态站点绑定对象”作为其参数。动态站点绑定对象是与具体语言相关的比如IronPython和C#都有各自的动态站点绑定对象。动态站点绑定对象的主要工作是将代码中的动态表达式本例中为d转换为一棵“抽象语法树ASTAbstract Syntax Tree”这棵语法树被称为“DLR Tree”是在.NET 3.5所引入的LINQ表达式树的基础上扩充而来的因此有时又称其为“表达式树Expression Tree”DLR在内部调用此表达式树的Compile()方法生成IL指令得到一个可以被CLR所执行的委托在本例中其类型就是FuncCallSite, object, object。动态调用站点对象本例中为p__Site1有一个Target属性它负责引用这一生成好的委托。委托生成之后动态表达式的执行就体现为委托的执行其实参由C#编译器直接“写死”在IL代码中。简化的代码示意如下通过Reflector得到为便于阅读修改了变量名object d 100;object CS$0$0000 d;if (p__Site1 null)p__Site1 CallSiteFuncCallSite, object, object.Create(……);d p__Site1.Target(p__Site1, CS$0$0000);上述类型推断、方法绑定及IL代码生成的工作都是在程序运行时完成的。4动态代码很慢吗动态编程语言易学易用代码紧凑开发灵活但性能则一直是它的“软肋”。为了提升性能DLR设计了一个三级缓存策略。动态站点绑定对象会为动态调用表达式转换而成的语法树加上相应的测试条件称为“test”构成一个“规则Rule”这个规则可以用于判断某个语法树是否可用于特定的动态调用表达式。举个例子请看以下这个动态表达式d1 d2如果在程序运行时d1和d2都是int类型的整数则DLR生成的规则为if( d1 is int d2 is int) //测试条件return (int)d1(int)d2; //语法树DLR通过检查规则中的“测试条件”就可以知道某个动态表达式是否可以使用此规则所包容的语法树。“规则”是DLR缓存的主要对象。前面介绍过的动态站点对象Target属性所引用的委托是第一级缓存它实现的处理逻辑是这样的