运行时查找
在运行时,动态操作将根据目标对象d的本质进行分派——
COM对象
如果d是一个COM对象,则操作通过COM IDispatch进行动态分派。这允许调用没有主互操作程序集(Primary Interop Assembly,PIA)的COM类型,并依赖C#中没有对应概念的COM特性,如索引属性和默认属性。
动态对象
如果d实现了IDynamicObject接口,则请求d自身来执行该操作。因此通过实现IDynamicObject接口,类型可以完全重新定义动态操作的意义。这在动态语言——如IronPython和IronRuby——中大量使用,用于实现他们的动态对象模型。API也会使用这类对象,例如HTML DOM允许直接使用属性语法来访问对象的属性。
简单对象
除此之外,则d是一个标准的.NET对象,操作是通过在其类型上进行反射来分派的,C#的“运行时绑定器(runtime binder)”实现了运行时的C#查找和重载解析。其背后的本质是将C#编译器作为运行时组件运行,来“完成”被静态编译器延迟的动态操作。
示例
考虑下面的代码——
dynamic d2 = new Bar();
string s;
d1.M(s, d2, 3, null);
“使用下面的参数执行一个称作M的实例方法——
- 1. 一个string
- 2. 一个dynamic
- 3. 一个int字面值3
- 4. 一个object字面值null”
在运行时,假设d1的实际类型Foo不是COM类型,也没有实现IDynamicObject。在这种情况下,C#运行时绑定器担负起了重载解析的工作,这是基于运行时类型信息完成的,按照下面的步骤进行处理——
- 1. 使用反射获取两个对象d1和d2的实际运行时类型,它们没有静态类型(包括静态类型dynamic)。结果为d1是Foo类型而d2是Bar。
- 2. 使用普通的C#语义在Foo类型上对M(string,Bar,3,null)调用进行方法查找和重载解析。
- 3. 如果找到了该方法,则调用它;否则抛出运行时异常。
带有动态参数的重载解析
即便方法调用的接受者是静态类型的,重载解析依然发生在运行时。当一个或多个实参是dynamic类型时就会出现这种情况——
dynamic d = new Bar();
var result = foo.M(d);
C#运行时绑定器会基于d的运行时类型——也就是Bar——在Foo上M方法的静态可知(statically known)重载之间进行选择。其结果是dynamc类型。
动态语言运行时
动态语言运行时(Dynamic Language Runtime,DLR)是动态查找的底层实现的一个重要组件,也是.NET 4.0中新增的API。
DLR不仅为C#动态查找,还为很多其他.NET上的动态语言——如IronPython和IronRuby——的实现提供了底层的基础设施。这一通用基础设施确保了高度的互操作性,更重要的是,DLR提供了卓越的缓存机制,使得运行时分派的效率得到巨大的改善。
对于使用C#动态查找的用户来说,除了更高的性能之外,根本感觉不到DLR的存在。不过,如果你希望实现自己的动态分派对象,可以使用IDynamicObject接口来与DLR互操作,并向其中插入自己的行为。这是一个非常高级的任务,要求对DLR的内部工作原理有相当深入的了解。对于编写API的人,值得在这些问题上花些功夫,这样能够更广泛地改善可用性,例如为一个本身就是动态的领域编写类库。