C# 4,最大的创新点是拥有了动态编程语言的特性(2)
只要给.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代理对象。
(3)C# 4动态编程技术内幕
C#4中所定义的dynamic变量可以引用以下类型的对象:
传统的“静态”的CLR对象。
COM包装器对象。前面已经介绍了这方面的内容。
实现了IDynamicMetaObjectProvider接口的“动态对象”,ExpandoObject就是这种类型对象的实例。
基于DLR实现的动态语言(比如IronRuby和IronPython)所创建的对象。
从C#程序员角度来看,所有这四种对象都是一样的,都可用一个dynamic变量引用之,而DLR在程序运行时动态地将方法调用和字段存取请求“绑定”到真正的对象上。
dynamic的功能是由DLR所支撑的,是C#编译器与DLR分工合作的成果。
请看以下示例代码:
dynamic d = 100;
d++;
C#编译器在处理上述代码时,它并不去检查变量d是否可以支持自增操作,而是为其创建了一个CallSite
private static class o__SiteContainer0 {
public static CallSite> <>p__Site1;
}
中文MSDN将CallSite
动态站点对象通过CallSite
动态站点绑定对象是与具体语言相关的,比如IronPython和C#都有各自的动态站点绑定对象。
动态站点绑定对象的主要工作是将代码中的动态表达式(本例中为d++)转换为一棵“抽象语法树(AST:Abstract Syntax Tree)”,这棵语法树被称为“DLR Tree”,是在.NET 3.5所引入的LINQ表达式树的基础上扩充而来的,因此,有时又称其为“表达式树(Expression Tree)”
DLR在内部调用此表达式树的Compile()方法生成IL指令,得到一个可以被CLR所执行的委托(在本例中其类型就是Func)。
动态调用站点对象(本例中为<>p__Site1)有一个Target属性,它负责引用这一生成好的委托。
委托生成之后,动态表达式的执行就体现为委托的执行,其实参由C#编译器直接“写死”在IL代码中。
简化的代码示意如下(通过Reflector得到,为便于阅读,修改了变量名):
object d = 100;
object CS$0$0000 = d;
if (<>p__Site1 == null)
<>p__Site1 = CallSite>.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属性所引用的委托是第一级缓存,它实现的处理逻辑是这样的:
//当前处理规则,属于第1级缓存
if( d1 is int && d2 is int) //测试条件
return (int)d1+(int)d2; //满足测试条件,直接返回一个表达式树
//未命中,则在第2级、第3级缓存中查找,如果找到了,用找到的结果更新第1级缓存
return site.Update(site,d1,d2);