在动态编程时,我们常常需要运行时确定调用对象的哪个属性或哪个方法。这个任务通常可以用反射来解决。但众所周知,反射的性能要比静态指定的方式低很多,因为反射要通过运行时复杂的机制完成。能否获得性能和灵活性兼备的动态调用?我在开发VBF的最新功能时反复考虑了这个问题。我们通常动态调用一个对象的属性是采用这样的手法,假设对象a有一个属性叫做MyProp:
Type t = a.GetType();
PropertyInfo pi = t.GetProperty("MyProp");
string value = (string)pi.GetValue(a, null);
注意到什么问题了吗?我们知道这个属性的类型是string,也知道它没有参数。当然也有不知道即将调用的属性类型及参数的时候,但这个场合我们知道,却没有利用,还是当成什么信息都不知道一样使用纯动态的手法获取。这样我们就错失了能利用强类型特性加速这一过程的良机。同样还有方法调用,我们有时候只是方法或属性的名字在编译时不知道(比如需要用户指定),但方法或属性的类型及签名我们是知道的,这种情况下就可以用泛型和委托技术高性能地调用。
泛型技术为处理类型提供了方便,除此之外,.NET的委托还具有一些额外的良好特性。委托可以担当类似接口的任务,但与接口最大的不同就在于,方法无须声明自己满足某个委托,而只要签名符合,即可赋给委托变量。这样我们就可以利用一组事先声明的委托,处理千变万化类型的属性与方法。
C#不允许属性带有参数,除非是索引器。VB允许属性带有参数但很少有人真的大量使用。于是在真实世界中属性的getter和setter的形式就被限定了,绝大部分属性的getter和setter可以用以下两个委托表示:
public delegate void PropertySetter<T>(T value);
public delegate T PropertyGetter<T>();
有了这两个委托,我们就可以对已知类型但名字需要动态化的属性进行高速的强类型动态访问了。方法是使用反射获取属性Gettet或Setter的MethodInfo,再使用MethodInfo创建委托:
Type t = a.GetType();
PropertyInfo pi = t.GetProperty("MyProp");
MethodInfo getter = pi.GetGetMethod();
PropertyGetter<string> strPropGetter =
(PropertyGetter<string>)Delegate.CreateDelegate(
typeof(PropertyGetter<string>), a, getter);
string value = strPropGetter();
注意,这个方法在调用前进行了更多反射操作,因此,如果你只想一两次地获取属性的值,这种方法还不如直接用放射来的快。但是,当你需要对同一属性进行成千上万次访问时,绝对值得多写这点代码,在string类型的简单属性上,速度可比直接反射获取最多快达1000倍,这是我实测的结果。
接下来我们讨论有index的属性和方法的调用。C#尽允许在索引器的语法上使用属性参数,而在VB看来,索引器不过是类所有带参数的属性中比较特殊的一个,他得到了在对象上使用数组语法访问的特权。不管怎么说,无论是索引器还是普通带参数的属性,他们的getter和setter过程都不像典型属性那样简单。同样还有对方法的调用,方法的签名千变万化,似乎我们很难用预先定义的委托统一进行调用。事实的确如此,不过与针对每一种属性访问器或方法的签名定义一种委托的做法相比,泛型还是给出了一种稍微舒服一点的做法:
public delegate R Func<R>();
public delegate R Func<T0, R>(T0 a0);
public delegate R Func<T0, T1, R>(T0 a0, T1 a1);
public delegate R Func<T0, T1, T2, R>(T0 a0, T1 a1, T2 a2);
这样一组泛型委托,可以涵盖参数数目从0-3,有返回值并且没有参数是out或ref的所有方法签名。你还可以定义一组用于无返回值的。有了这样一组泛型委托,就可以在想要某种函数的签名时直接创建出来,而无须声明新的类型。再结合刚才的手法,就可以用统一的手法实现大部分带有参数的属性或方法的动态调用——同时获得动态名称和强类型性能的双重好处。
也许你早已经利用了类似的手法,并用于除了动态调用属性或方法以外的其他任务。我只是在开发VBF时想到了他们,希望能对部分需要的人有所帮助。