[Dart翻译]用镜子在飞镖中的反射。简介

原文地址:www.dartcn.com/articles/se…

原文作者:

发布时间:2012年11月 (2013年11月更新)

注意:这篇文章只适用于1.x Dart SDK下的独立虚拟机。我们不建议在网络应用中使用镜像,而且Flutter SDK不支持dart:mirrors库。

Dart中的反射是基于镜像的概念,它是简单的反映其他对象的对象。在一个基于镜像的API中,每当人们想对一个实体进行反射时,必须获得一个单独的对象,称为镜像。

基于镜像的反射式API在安全、分发和部署方面有很大的优势。另一方面,使用它们有时会比旧的方法更加繁琐。

关于基于镜像的反射的原理的全面介绍,请参见本文末尾的参考文献。然而,如果你不想的话,你不需要深入研究这些,你真正需要知道的关于Dart的镜像API将在这里涵盖。

注意事项1:Dart的镜像API是不断发展的;虽然大部分自省API是稳定的,但在未来会有一些补充和调整,甚至在1.0之后。

目前,只有部分计划中的API已经实现。现有的部分是关于自省的,即程序发现和使用其自身结构的能力。自省API已经在Dart虚拟机上基本实现。

自省API被声明在名为dart:mirrors的库中。如果你想使用自省,请导入它。

import 'dart:mirrors';
复制代码

为了说明问题,我们假设你已经定义了下面这个类。

class MyClass {
  int i, j;
  int sum() => i + j;

  MyClass(this.i, this.j);

  static noise() => 42;

  static var s;
}
复制代码

获得镜像的最简单方法是调用顶层函数reflect()

注意事项2:目前,只有当反射代码和被反射的对象在同一个隔离区内运行时,反射才能发挥作用。在未来,我们希望扩展API以支持跨隔离区的反射。

reflect()方法接收一个对象并返回其上的InstanceMirror

InstanceMirror myClassInstanceMirror = reflect(new MyClass(3, 4));
复制代码

InstanceMirror是Mirror的一个子类,是镜像层次结构的根。一个InstanceMirror允许人们在一个对象上调用动态选择的代码。

InstanceMirror f = myClassInstanceMirror.invoke(#sum, []);
// Returns an InstanceMirror on 7.
复制代码

invoke()方法接收一个代表方法名称的符号(在本例中是#sum),一个位置参数列表,以及(可选)一个描述命名参数的映射。

为什么invoke()不接受一个代表方法名的字符串?因为最小化。最小化是指为了减少下载量而对网络程序中的名称进行处理的过程。

符号被引入Dart,以帮助反射在最小化的情况下工作。符号的最大优势是,当Dart程序被最小化时,符号也会被最小化。由于这个原因,镜像API使用的是符号而不是字符串。你可以在符号和字符串之间进行转换;通常,你这样做是为了打印出声明的名称,我们将在下面看到。

假设你想打印出一个类中的所有声明。你将需要一个ClassMirror,正如你所期望的那样,它能反映一个类。获取类镜像的一种方法是从实例镜像中获取。

ClassMirror MyClassMirror = myClassInstanceMirror.type; // Reflects MyClass
复制代码

另一种方法是使用顶层函数reflectClass()

ClassMirror cm = reflectClass(MyClass); // Reflects MyClass
复制代码

一旦我们通过各种方式获得了一个类镜像cm,我们就可以打印出cm所反映的类的所有声明的名称。

for (var m in cm.declarations.values) print(MirrorSystem.getName(m.simpleName));
复制代码

ClassMirror有一个getter declarations,返回一个从被反射类的声明名称到这些声明的镜像的地图。该地图包含了该类源代码中明确列出的所有声明:它的字段和方法(包括getters、setters和常规方法),不管它们是否是静态的,以及所有条形的构造函数。该地图不包含任何继承的成员,也不包含任何合成的成员,例如为字段自动生成的getters和setters。

我们从地图中提取值;每个值都将是MyClass的一个声明的镜像,并支持返回声明名称的getter simpleName。返回的名称是一个符号,所以我们必须将其转换为字符串,以便打印。静态方法MirrorSystem.getName为我们做到了这一点。

显然,在这种情况下,我们知道MyClass中的声明是什么;重点是,上面的for循环对任何类的镜像都有效,因此我们可以用它来打印任何类的声明。

printAllDeclarationsOf(ClassMirror cm) {
  for (var m in cm.declarations.values) print(MirrorSystem.getName(m.simpleName));
}
复制代码

镜像API中的一些方法以类似方式返回地图。这些地图允许你按名称查找成员,遍历所有名称,或遍历所有成员。事实上,有一个更简单的方法来完成我们刚刚做的事情。

printAllDeclarationsOf(ClassMirror cm) {
  for (var k in cm.declarations.keys) print(MirrorSystem.getName(k));
}
复制代码

如果我们想反射性地调用静态代码怎么办?我们也可以在ClassMirror上调用invoke()。

cm.invoke(#noise, []); // Returns an InstanceMirror on 42
复制代码

事实上,invoke()被定义在ObjectMirror类中,这是一个常见的镜像类的超类,它反映了具有状态和可执行代码的Dart实体,如常规的实例、类、库等。

下面是一个完整的例子,包含了我们到目前为止所做的事情。

import 'dart:mirrors';

class MyClass {
  int i, j;
  void my_method() {  }

  int sum() => i + j;

  MyClass(this.i, this.j);

  static noise() => 42;

  static var s;
}

main() {
  MyClass myClass = new MyClass(3, 4);
  InstanceMirror myClassInstanceMirror = reflect(myClass);

  ClassMirror MyClassMirror = myClassInstanceMirror.type;

  InstanceMirror res = myClassInstanceMirror.invoke(#sum, []);
  print('sum = ${res.reflectee}');

  var f = MyClassMirror.invoke(#noise, []);
  print('noise = $f');

  print('\nMethods:');
  Iterable<DeclarationMirror> decls =
      MyClassMirror.declarations.values.where(
        (dm) => dm is MethodMirror && dm.isRegularMethod);
  decls.forEach((MethodMirror mm) {
    print(MirrorSystem.getName(mm.simpleName));
  });

  print('\nAll declarations:');
  for (var k in MyClassMirror.declarations.keys) {
    print(MirrorSystem.getName(k));
  }

  MyClassMirror.setField(#s, 91);
  print(MyClass.s);
}
复制代码

下面是输出结果。

sum = 7
noise = InstanceMirror on 42

Methods:
my_method

sum

noise

All declarations:
i
j
s
my_method

sum
noise
MyClass
91
复制代码

在这一点上,我们已经向你展示了足够的东西,可以开始了。接下来还有一些你应该注意的事情。

注意事项3:你部署的东西往往比你写的东西少。这可能会以恼人的方式与反射发生交互。

由于网络应用程序的大小需要被控制,部署的Dart应用程序可能会被最小化和树状晃动。我们在上面讨论了最小化;摇树是指消除不被调用的源代码。这两个步骤一般都不能检测到代码的反射性使用。

这种优化在Dart中是一个事实,因为需要部署到JavaScript。我们需要避免在每个用Dart编写的网页上下载整个Dart平台。Tree shaking通过检测源代码中实际调用的方法名来做到这一点。然而,基于动态计算的符号而调用的代码无法通过这种方式被检测到,因此会被淘汰。

上述情况意味着,在运行时存在的实际代码可能与你在开发时的代码不同。你只用反射的代码可能不会被部署。运行时反射只知道运行时在运行程序中实际存在的东西。这可能会导致意外的发生。例如,人们可能试图以反射方式调用一个存在于源代码中的方法,但由于不存在非反射式的调用而被优化掉。这样的调用将导致对noSuchMethod()的调用。树的摇动对结构反省也有影响。同样,一个库或类型在运行时有哪些成员,可能与源代码中的内容不一致。

在有镜像的情况下,人们可以选择更加保守。不幸的是,由于人们可以为应用程序中的任何对象获得镜像,应用程序中的所有代码都必须被保留,包括Dart平台本身。相反,我们可以选择把这种调用当作方法在源代码中从未存在过。

我们正在试验一些机制,让程序员指定某些代码可能不会被树状摇动所消除。目前,你可以使用MirrorsUsed注解来达到这个目的,但我们预计随着时间的推移,细节会有很大的变化。

注意事项4:我们可以向你保证的是,MirrorsUsed将会改变。如果你使用它,请准备好应对突发变化。

上述内容应该足以让你开始使用镜像。自省API还有很多内容;你可以探索API,看看还有哪些内容。

我们希望在未来支持更强大的反射功能。这些功能包括镜像构建器,旨在允许程序扩展和修改自己,以及一个基于镜像的调试API。

参考文献

Gilad Bracha和David Ungar. 镜像。面向对象编程语言的元级设施的设计原则。在ACM面向对象编程、系统、语言和应用会议上,2004年10月。

Gilad Bracha. 通过镜子的语言学反思. 2010年1月在波茨坦HPI演讲的截屏。57分钟。

这些关于镜像的博文也可能被证明是有用的(而且消化起来比较不费时间)。


通过www.DeepL.com/Translator(免费版)翻译

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享