19

反射究竟是什么?我阅读了有关此主题的 Wikipedia 文章,并了解它是一种元编程,程序可以在运行时自行修改,但这意味着什么?在什么样的情况下这是一个好方法,什么时候最好使用它?

4

11 回答 11

26

反射是一种工具,您可以在其中查询对象在运行时的属性。例如,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 的“鸭子”类型系统那样在运行时添加新的实例变量。但是,您可以反思地更新在类型定义中声明的实例变量的值。

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

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

于 2009-05-14T16:26:03.273 回答
22

与其说是在执行时修改代码,不如说是检查对象并要求它们在不知道静态类型的情况下执行代码。

一种简单的描述方式是“一种使静态类型语言动态行为的有点痛苦的方式”。

编辑:用途:

  • 配置(例如,获取一个指定类型和属性的 XML 文件,然后构造适当的对象)
  • 测试(由名称或属性标识的单元测试)
  • Web 服务(至少在 .NET 中,核心 Web 服务引擎中使用了很多反射)
  • 自动事件关联 - 提供具有适当名称的方法,例如SubmitButton_Click,ASP.NET 将附加该方法作为SubmitButton'sClick事件的处理程序(如果您打开了自动关联)

这是个好主意吗?好吧,只有当替代方案很痛苦时。我更喜欢静态类型,因为它不会妨碍它——然后你会得到很多编译时的好处,而且它也更快。但是当你确实需要它时,反射可以让你做很多事情,否则这些事情是不可能的。

于 2009-05-14T16:26:54.503 回答
3

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

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

于 2009-05-14T16:26:14.037 回答
3

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

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

基本思想是我们可以在核心服务中使用反射来执行我们存储在定义调度的数据库中的方法。在实践中它非常整洁。我们的核心服务是可靠的,可以根据需要处理执行作业,而实际的工作库可以根据需要进行扩展和更改,而无需核心甚至关心。

如果您需要进一步的解释,请随时询问。这只是我能想到的最好的方式来解释一个真实世界的场景,在这个场景中,反射确实让我们的事情变得更容易。

于 2009-05-14T16:28:51.893 回答
3

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

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

MS 使用反射的一个很好的例子是数据对象上的数据绑定。您为要绑定到下拉列表等的对象指定文本字段和数据字段的名称,代码会反映对象并提取适当的信息。数据绑定对象一遍又一遍地执行相同的过程,但它不知道它必须绑定什么类型的对象。反射是一种编写一点代码来处理所有可能情况的便捷方式。

于 2009-05-14T17:08:43.467 回答
2

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

SqlDataReader sdr = Helper.GetReader("GetClientByID", ClientID);
Client c = new Client();
FillObject(sdr, c);
return c;
于 2009-05-14T16:31:05.490 回答
1

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

于 2009-05-14T16:26:04.567 回答
1

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

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

于 2009-05-14T16:26:10.637 回答
1

我给你举个例子。

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

这也意味着我已经能够使用依赖注入从头到尾使用它来显示有关数码照片的信息,只需交换数据库类即可。

于 2009-05-14T16:27:16.140 回答
1

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

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

于 2009-05-14T16:39:38.513 回答
1

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

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