首先了解什么是动态绑定与静态绑定
静态绑定
Duck d = ...
d.Quack();
- 静态绑定:最简单的情形是,编译器检查Duck中无参的Quack方法进行绑定。如果绑定失败,编译器会将搜索范围扩大到具有可选参数的方法、Duck基类中的方法以及扩展方法。如果还是没有找到,编译器会产生一个错误,无论绑定的是一个什么东西,底线是这个绑定是由编译器进行的,而且绑定是完全依赖于d这个操作数。这就是所谓的静态绑定。
动态绑定
dynamic d = ...
d.Quack();
- 动态绑定将类型绑定(类型解析、成员和操作过程)从编译时推迟到了运行时。在编译时,如果程序员知道某个特定函数、成员的存在而编译器不知道,那么这种操作是非常有用的,这种情况通常出现在操作动态语言和COM,如果不适用动态绑定,就只能使用反射(reflection)机制。
- 动态类型是通过dynamic关键字声明的。
- dynamic关键字其实就是告诉编译器放过这次静态类型的检查,让它在运行时在检查。动态对象是基于运行时进行绑定的,而不是基于编译时,当编译器看到dynamic时,它所做的事情仅仅是对表达式进行一个打包,具体的绑定发生在运行时。
动态绑定又分为两种 :自定义绑定和语言绑定
自定义绑定
自定义绑定发生在当一个动态对象实现了IDynamicMetaObjectProvider接口时。虽然你可以自己定义一个实现了该接口的对象,这个对象也可以这样用,但是更普通的情形是从一种在DLR上用.NET语言已经实现了的动态语言中获取这个对象,例如:IronPython or IronRuby。这些对象已经隐式的实现了IDynamicMetaObjectProvider接口。
using System;
using System.Dynamic;
public class Test
{
static void Main()
{
dynamic d = new Duck();
d.Quack(); // Quack method was called
d.Waddle(); // Waddle method was called
}
}
public class Duck : DynamicObject
{
public override bool TryInvokeMember (
InvokeMemberBinder binder, object[] args, out object result)
{
Console.WriteLine (binder.Name + " method was called");
result = null;
return true;
}
}
Duck类型实际上根本没有那两个方法,相反,它使用自定义绑定拦截并解释所有的方法调用。
语言绑定
语言绑定实在一个动态对象未实现IDynamicMetaObjectProvider接口时出现的。语言绑定在处理类型设计有缺陷和对付.NET固有类型的内在限制时非常有用。使用数值类型的一个常见问题是他们没有共同的接口,我们已经知道方法是可以动态绑定的,运算符也可以进行动态绑定:
static dynamic Mean (dynamic x, dynamic y)
{
return (x + y) / 2;
}
static void Main()
{
int x = 3, y = 4;
Console.WriteLine (Mean (x, y));
}
明显的好处是不用为每个不同的值类型提供不同的实现,缺点是在运行时会发生异常以及没有静态类型检查导致的安全性的缺失。
提示:动态绑定会破坏静态类型的安全性,但不会影响运行时的类型安全性。与反射机制不同,不能通过动态绑定绕过成员访问规则的限制。
可以通过设计将语言运行时的绑定效果达到静态绑定的效果,是动态对象的运行时类型能够在编译器确定。在前一个例子中,如果我们直接在Mean中处理int类型,结果是一样的(意思是将dynamic改成int。)静态绑定和动态绑定之间最显著的差异是扩展方法,将在后面的章节中进行讨论。
提示:动态绑定也会对性能造成影响。因为DLR的缓存机制对反复调用一个动态表达式进行了优化,允许在一个循环中高效的调用。这个优化后的机制能够使一个动态表达式的处理负载降低在100ns以内。