当前位置:首页 » 其他

发现一篇好文章,今天在研读

2005-06-19 23:16 本站整理 浏览(6)

标题:避开反射中常见的陷阱,雕琢高效的应用

这时MSDN杂志2005年第6期上的一篇文章,看读者对该文章的评价很高,大约是深入浅出的那种。在此研读一下,顺便翻译出来。

文章说明:本文中的部分内容基于予发布的.NET Framework 2.0版本,这部分内容将来有可能改变。

本文讨论:

1、如何获得优秀的反射性能

2、早期绑定和晚期绑定触发

3、成员缓存和处置

4、反射的最佳实践

高效率的使用反射一向如同API那样有争议。您在使用它时必须做出某种程度上的让步。然而这样做是值得的,因为反射式.NET中一项非常强大的用于开发可扩展应用的功能。使用反射,您可以加载类型,理解其中的成员,对它们做出判断,然后执行,所有这一切都是运行时的,安全的,受管理的。然而明智的使用这些功能,理解其中的陷阱和要付出的代价是非常重要的。

本文中,我将向您展示哪些反射是高成本的,哪些不是。我将指导您如何在寻求代价/收益的平衡中做出决定,同时也会深入到.NET的内部,做近距离的观察。尔后,我会介绍在.NET2.0中关于反射的新东西以及它如何使得完成特定的功能付出更小的代价。

哪些慢,哪些不慢?图1列出了常用的反射的API,正如您所看到的,其中一些是轻量的,另外一些则笨重。通常轻量的功能(函数)因为以下一些原因运行的更快:要么反射的基础结构已经将运行所需要的信息全部存在超高速的运行时数据结构中了,要么就是即时(JIT)编译器监测到这些方法调用和代码模式,并做出了特殊的优化,这样可以稍微提高一点性能。两个好的JIT编译器优化反射的例子是C#的Typeof方法和基础类型库(BCL)的Object.GetType方法。两者在BCL中heavily(频繁?)的使用用于比较类型相等,这样,它们必须特殊地关照以保证性能的优化。然而,本文中,我将关注那些低效率的反射API。

您在什么时候使用反射? 您应当经常仔细考虑您如何使用反射。在没有严格的性能标准的要求下,偶尔使用一下反射可能没问题。如果在您调用应用的一部分时只能采用反射API,而那一部分是加载和调用的第三方插件,这时,调用的代价就必须考虑了。况且,如果您的情形还包括一个高容量的ASP.NET站点,而该站点要求良好的吞吐率和响应时间,并且在您的“快速路”(指的应用中那些必须运行的非常快,而且经常要用到的代码)上采用重量的反射API是有某些含义的[也就是说,应用的需求要求的?用到这些反射],这时您就需要认真对应用架构做一个检讨以决定您在这里使用反射是否正确。

作为一个经典例子,设想您正在写一个应用,该应用作为第三方插件的宿主。深入到插件中的调用是否出现在您的程序中不经常动作的代码区域或者它们需要经常被调用?还有,考虑如果您的应用必须迅速执行,但是要求用到反射?

当考虑使用反射时,首先考虑的是您的应用的可扩展性的要点能否静态定义为借口或者基类。在扩展点上调用成员最为干净利索和高效的方式是静态地定义一个与约定让使用者去实现,然后安全地使用这个约定。通常,这个约定由一个接口或者基类来定义,使用者有一个实现它的途径。为了演示,我创建了一个示例,该示例有一个扩展的途径用于插入到日志(logging)基础结构。

首先,我为我的应用的日志的需求定义了一个约定。我的应用将会调用3种不同的方法用于记录3种不同类型的消息:安全、应用和错误,因此我将约定定义为一个带有3种方法的接口:

public interface LogInterface

{

void WriteErrorEvent(string errorMessage);

void WriteApplicationEvent(string applicationMessage);

void WriteSecurityEvent(string securityMessage);

}

public interface LogInterface

{

void WriteErrorEvent(string errorMessage);

void WriteApplicationEvent(string applicationMessage);

void WriteSecurityEvent(string securityMessage);

}该接口可以被编译为一个DLL[没错,我验证了一下,使用csc /t:library it.cs这个命令就可以编译成一个名为it.dll的文件,而且该文件可以通过对象浏览器看到其中的接口。]并且分发给那些希望为我的应用提供日志结构的第三方。第三方可以使用这个约定实现他们的日志结构;使他们的插件库可以被我的应用使用。下面的代码显示了一个第三方日志插件,它将所有的日志消息写到控制台:

public class JoelsLogger : LogInterface

{

public void WriteErrorEvent(string errorMessage) {

Console.WriteLine("error: " + errorMessage);

}

public void WriteApplicationEvent(string applicationMessage) {

Console.WriteLine("application: " + applicationMessage);

}

public void WriteSecurityEvent(string securityMessage) {

Console.WriteLine("security: " + securityMessage);

}

}

既然我们已经有了一个约定和实现这个约定的库,我就需要编码来发现和加载插件,接口的发现,类型的实例化和调用。下面是这个应用的一个小的程序片断:

Assembly asm = Assembly.LoadFrom(@"c:/myapp/plugins/joelslogger.dll"); //注意这里使用的是LoadFrom。

LogInterface logger = null;

foreach (Type t in asm.GetTypes()) {

if (t.GetInterface("LogInterface") != null) {

logger = (LogInterface)Activator.CreateInstance(t);

break;

}

}

if (logger != null) logger.WriteApplicationEvent("Initialized...");

显然,这么做不是明智的。对于发现什么类型实现了接口来说,它是一种笨拙的方法,因为它反复每一个类型并要求加载器去加载它。

[未完待续……]