什么是反思,何时是一个好方法?

时间:2009-05-14 16:22:03

标签: reflection metaprogramming definition

反思究竟是什么? 我阅读了关于这个主题的维基百科文章,我理解它是一种元编程,程序可以在运行时自行修改,但这意味着什么? 在什么样的情况下这是一个好的方法,何时最好使用它?

11 个答案:

答案 0 :(得分:24)

Reflection是一种可以在运行时查询对象的属性的工具。例如,Python,Java和.Net具有可以找到对象的实例变量或方法的工具。

反射应用程序的一个示例是O / R映射层。有些人使用反射通过在运行时查询其属性并动态填充实例来构造对象。这允许您基于来自某种数据字典的元数据以编程方式执行此操作,而无需重新编译应用程序。

举一个简单的例子,我将使用Python,因为它的反射工具使用起来非常简单,并且比java或.Net的反射设备更少。

ActivePython 2.5.2.2 (ActiveState Software Inc.) based on
Python 2.5.2 (r252:60911, Mar 27 2008, 17:57:18) [MSC v.1310 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> class foo:
...     def __init__(self):
...             self.x = 1
...
>>> xx = foo()      # Creates an object and runs the constructor
>>> xx.__dict__     # System metadata about the object
{'x': 1}
>>> a = xx.__dict__ # Now we manipulate the object through 
>>> a['y'] = 2      # its metadata ...
>>> print xx.y      # ... and suddenly it has a new instance variable
2                   
>>>

现在,我们使用基本反射来检查任意对象的实例变量。 Python上的特殊变量__dict__是一个对象的系统属性,它具有由变量(或方法)名称键入的成员的哈希表。我们已经反思地检查了对象,并使用反射工具人工地将第二个实例变量放入其中,然后我们可以通过将其作为实例变量来显示它。

请注意,这个特殊技巧不适用于Java或.Net,因为实例变量是固定的。这些语言的类型系统不允许在运行时以python的'duck'类型系统的方式添加新的实例变量。但是,您可以反射性地更新在类型定义中声明的实例变量的值。

您还可以使用反射来动态构造方法调用并执行各种其他巧妙的技巧,例如基于参数实例化对象。例如,如果您有某种基于插件的系统,其中某些功能是可选的,您可以使用反射来查询插件有关它提供的服务(可能通过查询是否实现了某些接口)而不需要显式元数据。

许多动态语言接口(如OLE自动化)使用反射作为接口的组成部分。

答案 1 :(得分:20)

在执行时修改代码并不是那么多,而是检查对象并要求他们在不知道静态类型的情况下执行代码。

描述它的一种简单方法是“使一种静态类型语言动态表现的一种有点痛苦的方法。”

编辑:用途:

  • 配置(例如,获取指定类型和属性的XML文件,然后构造适当的对象)
  • 测试(通过名称或属性识别的单元测试)
  • Web服务(至少在.NET中,核心Web服务引擎中使用了很多反射)
  • 自动事件连线 - 提供具有适当名称的方法,例如: SubmitButton_Click和ASP.NET会将该方法作为SubmitButton的{​​{1}}事件的处理程序附加(如果您已启用自动装配)

这是个好主意吗?好吧,只有当替代品很痛苦时。我喜欢静态打字,但它不会妨碍 - 然后你会获得很多编译时的好处,而且它也会更快。但是当你需要它时,反射可以让你做各种其他事情,否则是不可能的。

答案 2 :(得分:3)

我能想到的第一个好例子就是当你需要在给定对象上执行一组方法而不知道在编译时它将存在哪些方法时。

以单元测试框架为例。负责运行所有单元测试的测试运行器类不会提前知道您要为方法命名的内容。它只知道它们将以“test”为前缀(或者在Java 5的情况下,用@Test注释)。因此,当给定一个测试类时,它会反映在该类上,以便获得其中所有方法的列表。然后,它将这些方法名称作为字符串进行迭代,并在对象上调用那些以“test”开头的方法。没有反思,这是不可能的。这只是一个例子。

答案 3 :(得分:3)

在我能想到的至少一个项目中,反思对我有用。我们编写了一个内部“流程管理器”程序,它以一定的时间间隔执行许多关键业务流程。该项目的设置使得核心实际上只是一个带有计时器对象的Windows服务,每隔30秒就会触发一次,并检查要做的工作。

实际工作是在类库(适当地称为“WorkerLib”)中完成的。我们使用执行某些任务的公共方法定义类(移动文件,将数据上传到远程站点等)。这个想法是核心服务可以从工作库调用方法,而不知道它调用的方法。这允许我们在数据库中为作业创建一个计划,甚至可以将新方法添加到类库中,而无需更改核心系统。

基本思想是我们可以在核心服务中使用反射来执行我们已经存储在定义日程表的数据库中的名称的方法。它在实践中非常整洁。我们的核心服务非常扎实,可以根据需要处理执行工作,而实际的工作库可以根据需要进行扩展和更改,而无需核心工作。

如果您需要进一步说明,请随时提出。这只是我能想到的最好的解释现实场景的方式,其中反射真的让我们的事情变得更容易。

答案 4 :(得分:2)

另一个例子:我使用的代码接受数据库的输出 - 这是一组带有命名列的行 - 并将其输入到一个对象数组中。我遍历行,如果目标对象具有相同名称和类型的属性,我设置它。这使我的数据获取代码看起来像这样:

SqlDataReader sdr = Helper.GetReader("GetClientByID", ClientID);
Client c = new Client();
FillObject(sdr, c);
return c;

答案 5 :(得分:2)

实际上,反射应该被认为是一种代码放大器。它可以使代码更好更清晰,代码更糟糕。它有什么作用?它真的允许你编写代码,你不完全确定在你编写代码时会做什么。您有一个大致的想法,但它允许您不编码在编译程序时要执行的对象,方法和属性。

其他帖子在说它允许程序根据配置值执行代码时是正确的,但它的真正力量在于它允许你严重弯曲面向对象编程的规则。这就是它的功能。这有点像关闭安全措施。私有方法和属性可以通过反射以及其他任何东西来访问。

MS使用反射时的一个很好的例子是数据对象的数据绑定。您指定要绑定到下拉列表等的对象的文本字段和数据字段的名称,代码反映该对象并提取相应的信息。数据绑定对象反复执行相同的过程,但它不知道它必须绑定的对象类型。反射是编写一些代码以处理所有可能情况的便捷方式。

答案 6 :(得分:1)

在Java中,它基本上是一种在不事先了解它的情况下实例化类的方法。假设您希望用户能够通过添加他们希望程序使用的类来更改配置文件(假设您有许多接口的实现)。通过反射,您可以根据其名称,方法签名等创建对象。 。然后将其转换为您的界面。

答案 7 :(得分:1)

反射对于运行时配置很有用,允许通过外部配置驱动系统的各个部分。

例如,类工厂可以基于输入文件构造不同的具体类型,其中具体类型需要不同的配置信息来调用具体的构造函数而不是使用构建器接口。 (使用反射定位对象的构造方法)。

答案 8 :(得分:1)

我会举个例子。

作为编程练习,我写了一个mp3文件检查器。它扫描我的音乐库并在DataGridView中显示我感兴趣的id1 / id2标签。我使用反射从mp3信息类中获取属性,而UI代码不必知道该类的任何信息。如果我想更改显示的信息,我可以编辑mp3信息类或更改其配置(取决于我如何编写类),而不必更新UI。

这也意味着我已经能够使用依赖注入来使用相同的结尾来显示有关数字照片的信息,只需交换数据库类。

答案 9 :(得分:1)

反射(基本上)是程序查询编译器可用的类型信息的能力。因此,例如,给定类型的名称,您可以查询它包含的方法。然后,对于每种方法,您可以查询它们所采用的参数类型等等。

对于运行时配置非常有用,您可以在其中获得指定应用程序行为的配置文件。配置可能包含您应该使用的具体类型的名称(通常是IOC容器的情况)。使用反射,您可以创建此具体类型的实例(通过反射API)并使用它。

答案 10 :(得分:1)

程序集包含模块,模块包含类型,类型包含成员。 Reflection提供了封装程序集,模块和类型的对象。您可以使用反射来动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型。然后,您可以调用类型的方法或访问其字段和属性。反射的典型用途包括:

  • 使用Assembly定义和加载程序集,加载程序集清单中列出的模块,并从此程序集中找到一个类型并创建它的实例。
  • 使用Module发现包含模块的程序集和模块中的类等信息。您还可以获取模块上定义的所有全局方法或其他特定的非全局方法。
  • 使用ConstructorInfo发现构造函数的名称,参数,访问修饰符(如公共或私有)和实现细节(如抽象或虚拟)等信息。使用Type的GetConstructors或GetConstructor方法来调用特定的构造函数。
  • 使用MethodInfo发现方法的名称,返回类型,参数,访问修饰符(如公共或私有)和实现细节(如抽象或虚拟)等信息。使用Type的GetMethods或GetMethod方法来调用特定方法。
  • 使用FieldInfo发现字段的名称,访问修饰符(如公共或私有)和实现细节(如静态)等信息,以及获取或设置字段值。
  • 使用EventInfo发现信息,例如名称,事件处理程序数据类型,自定义属性,声明类型和事件的反射类型,以及添加或删除事件处理程序。
  • 使用PropertyInfo发现信息,例如名称,数据类型,声明类型,反射类型,属性的只读或可写状态,以及获取或设置属性值。
  • 使用ParameterInfo发现参数名称,数据类型,参数是输入参数还是输出参数以及方法签名中参数的位置等信息。