【发布时间】:2010-07-31 09:40:30
【问题描述】:
我发现很多情况下我自己认为可以使用反射来解决问题,但我通常不会,因为我听到很多类似“不要使用反射,它太低效”的说法.
现在我遇到了一个问题,除了在new T() 中使用反射之外,我找不到任何其他解决方案,如this question & answer 中所述。
所以我想知道是否有人可以告诉我反射的具体预期用途,以及是否有一套指导方针来指示何时合适,何时不合适?
【问题讨论】:
标签: c# reflection
我发现很多情况下我自己认为可以使用反射来解决问题,但我通常不会,因为我听到很多类似“不要使用反射,它太低效”的说法.
现在我遇到了一个问题,除了在new T() 中使用反射之外,我找不到任何其他解决方案,如this question & answer 中所述。
所以我想知道是否有人可以告诉我反射的具体预期用途,以及是否有一套指导方针来指示何时合适,何时不合适?
【问题讨论】:
标签: c# reflection
代码效率的一个有用抽象是将其划分为三类时间,每类相隔大约 3 个数量级。
首先是人类时间。当您只需要让一个人对您的代码的性能感到满意时,您可以做很多事情。人类无法感知需要 10 毫秒或 20 毫秒的代码之间的区别,两者看起来都是即时的。当一个程序需要 6 秒而不是 5 秒,大约 3 0 亿 条机器指令时,人类是宽容的。以人工方式运行的程序的常见示例是编译器和点击式设计器。使用反射从来都不是问题。
然后是 I/O 时间。当您的程序需要访问磁盘或网络时。 I/O 很慢,在磁盘的情况下受到机械运动的限制,在网络的情况下受到带宽和延迟的限制。您总是可以判断 I/O 何时成为瓶颈,您的程序正在运行,但并没有过多地增加 CPU 负载。操作系统一直在阻塞线程,让它一直等到 I/O 请求完成。
反射在 I/O 时运行。要检索类型数据,CLR 必须读取程序集元数据。如果以前没有这样做,您的程序将导致页面错误,要求操作系统从磁盘读取数据。接下来是,粗略地说,反射可以使 I/O 绑定代码只慢两倍。通常更好,因为在第一次 perf 命中后,元数据被缓存并且可以更快地检索。因此,反射通常是一种可接受的折衷方案。典型的例子是序列化和 dbase ORM。
然后是机器时间。 CPU内核的原始性能是惊人的。一个属性 getter 可以在 0 到 1/2 纳秒之间的某处执行纳秒。这确实不与PropertyInfo.GetValue() 相比较。两者都会让 CPU 保持忙碌,您会看到核心的 CPU 负载为 100%。但是 GetValue() 花费数百甚至数千条机器代码指令。不计算在元数据中分页所需的时间。虽然增量时间不多,但在您循环时会快速累积。
如果您无法将反射代码分类为人工时间或 I/O 时间类别,则反射不太可能成为常规代码的合适替代品。
【讨论】:
防止反射减慢程序速度的关键是不要在循环中使用它。如果要在启动期间从对象读取属性(仅发生一次),请使用反射。您想从 10,000 个未知类型的对象的列表中读取一个属性,使用反射获取一次属性 getter 委托(搜索词:PropertyInfo.GetGetMethod),然后调用 10,000 个类型的委托。 StackOverflow 上有很多这样的例子。
【讨论】:
它通常“足够快”,如果您需要更快(对于紧密循环等),您可以使用 Expression 或 ILGenerator(可能通过 DynamicMethod)进行元编程,以使 非常 快速的代码(包括一些你在 C# 中做不到的技巧)。
反射更通常用于框架/库场景,其中库根据定义对调用者一无所知,并且必须基于配置、属性或模式工作。
【讨论】:
dynamic 的选项,它具有呼叫站点缓存等优化。所以在反射和元编程之间有一个中间步骤。事实上,我尽可能使用dynamic,而不是直接使用反射,只是为了可读性。
如果有一件事我讨厌听到它是“不要使用反射,它的效率太低了”。
效率太低了怎么办?如果您正在编写一个每月运行一次且时间要求不高的控制台应用程序,因为您使用了反射,因此需要 30 秒而不是 28 秒真的很重要吗?
什么时候不适合使用的指南只有你才能真正组合起来,因为它们在很大程度上取决于你在做什么以及替代品的效率/性能如何。
【讨论】:
反思并非低效。它比直接调用效率低。因此,当没有等效的编译时安全方法时,我个人使用反射。恕我直言,反射的问题不在于效率,而在于代码的脆弱性,因为它使用了对重构非常不友好的魔术字符串。
【讨论】:
我将它用于插件架构 - 在插件文件夹中查看程序集以查找标有自定义属性的方法,该属性指示有关插件的信息 - 并在日志框架中。框架检测程序集本身的自定义属性,该属性包含有关程序集作者、项目、版本信息和其他标记的信息,这些信息与堆栈跟踪中的所有内容一起记录。
打算泄露一个“商业机密”,但这是一个很好的。该框架允许您使用“Story ref”标记每个方法或类,例如
[StoryRef(Ref="ImportCSV1")]
...想法是它会集成到我们的敏捷项目管理框架中:如果在该类/方法中抛出任何异常,日志记录方法将使用反射来检查堆栈跟踪中的 StoryRef 属性,如果是这样,那将被记录为该故事的例外。在 PM 软件中,您可以通过 Story 看到异常(故事就像一个极端/敏捷的用例)。
我认为这是一个有效的用途,至少!基本上,当它看起来是最整洁且适当的方式时,我会使用反射。没有其他任何东西真正涉及到它 - 我想不出你会使用反射来进行如此多的调用来提高效率。
【讨论】:
所以我想知道是否有人可以告诉 我反思的具体目的 用法,如果有一组 指示何时发生的指南 合适,什么时候不合适?
反射的一个坏示例是from Wikipedia:
//Without reflection
Foo foo = new Foo();
foo.Hello();
//With reflection
Type t = Type.GetType("FooNamespace.Foo");
object foo = Activator.CreateInstance(t);
t.InvokeMember("Hello", BindingFlags.InvokeMethod, null, foo, null);
在这里,使用反射没有任何优势:不使用反射的代码不仅效率更高,而且更易于理解。
良好的反射的用途是诸如序列化和对象关系映射之类的东西,如果您有一个类的属性列表,它们很容易实现,但否则需要为每个类自定义编写的函数.
【讨论】: