[Dart笔记]Reflection & Symbol

949 阅读5分钟

使用反射

subscription.packtpub.com/book/web_de…

Dart基于镜像的反射API(包含在dart:mirrors库中)提供了一套强大的工具来反射代码。这意味着可以反省一个程序的完整结构,发现所有对象的所有属性。通过这种方式,方法可以被反射性地调用。甚至有可能动态地评估源代码中尚未指定的代码。这方面的一个例子是调用一个方法,该方法的名称被作为参数提供,因为它在数据库表中被查询到。

准备好

你的代码中使用反射的部分应该有以下导入代码:

import 'dart:mirrors';

如何做...

为了进行反射,我们进行以下操作:

  • 在反射项目中,我们使用一个类Embrace来进行反射。
void main() {
    var embr = new Embrace(5);
    print(embr);  // Embraceometer reads 5
    embr.strength += 5;
    print(embr.toJson()); // {strength: 10}
    var embr2 = new Embrace(10);
    var bigHug = embr + embr2;
    // Start reflection code:
  • 使用MirrorSystem,如以下代码所示:
    final MirrorSystem ms = currentMirrorSystem();
     // Iterating through libraries
    ms
       .libraries
       .forEach((Uri name, LibraryMirror libMirror){
          print('$name $libMirror');
       });

使用InstanceMirror和ClassMirror,如以下代码所示:

    InstanceMirror im = reflect(embr);
    InstanceMirror im2 = im.invoke(#toJson, []);
    print(im2.reflectee); // {strength: 10}
    ClassMirror cm = reflectClass(Embrace);
    ClassMirror cm2 = im.type;
    printAllDeclarationsOf(cm);
    InstanceMirror im3 = cm.newInstance(#light, []);
    print(im3.reflectee.strength);
    im3.reflectee.withAffection();
}

printAllDeclarationsOf(ClassMirror cm) {
  for (var k in cm.declarations.keys)
    print(MirrorSystem.getName(k));
  print(MirrorSystem.getName(m.simpleName));
}

class Embrace  {
   num _strength;
   num get strength => _strength;
   set strength(num value) => _strength=value;
   Embrace(this._strength);
   Embrace.light(): _strength=3;
   Embrace.strangle(): _strength=100;
   Embrace operator +(Embrace other) => new Embrace(strength + other.strength);
   String toString() => "Embraceometer reads $strength";
   Map toJson() => {'strength': '$_strength'};
   
   withAffection() {
     for (var no=0; no <= 3; no++) {
       for (var s=0; s <=5; s++) { strength = s; }
     }
   }
}
  • 运行前面的程序产生以下输出:
Embraceometer reads 5
{strength: 10}
dart:core LibraryMirror on 'dart.core'
dart:mirrors LibraryMirror on
'dart.mirrors'
dart:nativewrappers LibraryMirror on ''
dart:typed_data LibraryMirror on 'dart.typed_data'
dart:async LibraryMirror on 'dart.async'
dart:convert LibraryMirror on 'dart.convert'
dart:collection LibraryMirror on 'dart.collection'
dart:_internal LibraryMirror on 'dart._internal@0x1f109d24'
dart:isolate LibraryMirror on 'dart.isolate' dart:math LibraryMirror on 'dart.math' dart:builtin LibraryMirror on 'builtin' dart:io LibraryMirror on 'dart.io' file:///F:/Dartiverse/ADartCookbook/book/Chapter 4 - Object orientation/code/reflection/bin/reflection.dart LibraryMirror on ''
{strength: 10}
_strength
strength
strength=
+
toString
toJson
withAffection
Embrace
Embrace.light
Embrace.strangle
3

它是如何工作的...

currentMirrorSystem类返回当前隔离区的MirrorSystem对象;library getter提供当前代码范围内的库列表。

InstanceMirror子类是一个对象实例的表示,ClassMirror是类定义的表示。

在一个对象上使用顶级的reflect方法来获得InstanceMirror。这允许你在对象上动态地调用代码,产生另一个InstanceMirror;使用它的reflectee属性,你可以访问实际的实例。

请注意,invoke方法的第一个参数是方法名称的符号(可从其#前缀中识别)。符号是在Dart中引入的,因为它们可以在最小化过程中生存。

在一个类上的顶级reflectClass方法会产生ClassMirror;通过在该类的InstanceMirror上调用type,可以得到相同类型的对象。ClassMirror类有一个声明的getter,返回一个从声明的名字到它们的镜像的映射。静态方法可以在ClassMirror上调用。

对于Dart中的每一种类型的对象,都存在一个相应的镜像对象。所以我们有Variabl eMirror, MethodMirror, ClassMirror, LibraryMirror,等等。在ClassMirror类上调用newInstance,并将构造函数的名称作为一个符号,会产生InstanceMirror;然后你可以通过reflectee调用真实对象的方法。

还有更多...

在使用反射时,有一些事情我们应该注意:

  • 镜像API仍在发展中,所以预计未来会有一些补充和调整。对于在Dart虚拟机中运行的代码来说,该实现是最完整的。
  • dart2js中的镜像有点滞后。由dart2js执行的minifying和tree shaking应用程序的过程通常不会检测到反射的代码。因此在运行时使用反射可能会失败,导致noSuchMethod()错误。为了防止这种情况发生,可以使用Mirrors注解,如下面的代码所示,这有助于dart2js编译器生成更小的代码。
@MirrorsUsed(override:'*')
import 'dart:mirrors';
  • 其中一个限制是跨隔离区的反射。在写这本书的时候,只有当反射代码和被反射的对象在同一个隔离区内运行时,反射才能发挥作用。
  • 假设你有一个返回Future值的无文档的方法,你想知道该对象的属性和方法,而不用挖掘源代码。运行下面的代码片断:
import 'dart:mirrors';

undocumentedMethod().then((unknown){
      var r = reflect(unknown).type; // ClassMirror
      var m = r.declarations;
for (var k in m.declarations.key) print(MirrorSystem.getName(k));
}); 

参见

当你想在代码中使用反射时,必须进行最小化和树形摇动,请阅读第1章 "使用Dart工具 "中的缩小应用程序的配方

DartLangSpec-v2.2.pdf

dart.dev/guides/lang…

16.8 符号

符号字面表示一个名称,在Dart程序中是一个有效的声明名称或一个有效的库名称。

<symbolLiteral> ::= ‘#’ (hoperatori | (hidentifieri (‘.’ hidentifieri)*))

一个符号字面#id,其中id是一个不以下划线('_')开头的标识符,评估为一个代表标识符id的符号实例。所有#id的出现都是对同一个实例进行评估(符号实例是规范化的),没有其他的符号字面会对该Symbol实例或与该实例相等的Symbol实例进行评估(根据'=='操作符16.27)。

一个符号字面#id.id2...idn,其中id...idn是标识符,评估为代表该标识符序列的符号实例。所有具有相同标识符序列的#id.id2...idn的出现都是对同一个实例进行求值,没有其他的符号文字对该符号实例或对与该实例'=='的符号实例进行求值。这种符号文字表示一个库声明的名称。库名称不受库隐私的限制,即使它的一些标识符以下划线开始。

符号字面#operator评估为Symbol的一个实例,代表那个特定的operator名称。所有#operator的出现都是对同一个实例进行评估,其他的符号字面都不会对该Symbol实例或对与该实例'=='的Symbol实例进行评估。

符号字面意义#_id,评估到一个Symbol实例,代表包含库的私有标识符_id。在同一个库中,#_id的所有出现都是对同一个实例的评估,没有其他的符号字面会对该Symbol实例或对该实例'=='的Symbol实例的评估。

符号字面所创建的对象都覆盖了从Object类继承的'=='操作符。

人们很可能会问,引入字面符号的动机是什么?在一些语言中,符号是规范化的,而字符串则不是。然而字符串在Dart中已经被规范化了。符号比字符串更容易输入,而且它们的使用会让人奇怪地上瘾,但这还不足以成为在语言中添加字面形式的理由。主要的动机是与反射的使用和一种被称为最小化的网络具体做法有关。

最小化在整个程序中持续压缩标识符,以减少下载量。这种做法给那些通过字符串引用程序声明的反射性程序带来了困难。字符串会在源码中引用一个标识符,但该标识符在最小化后的代码中不再使用,使用这些的反射代码会失败。因此,Dart反射使用Symbol类型的对象而不是字符串。符号的实例被保证在最小化方面是稳定的。为符号提供字面形式使反射性代码更容易阅读和编写。符号很容易输入,并且通常可以作为枚举的方便替代物,这是次要的好处。

符号字面的静态类型是符号。

Dart API reference documentation -- Symbol

api.dart.dev/stable/dart…

从相同名称创建的符号实例是平等的,但不一定相同,但作为编译时常量创建的符号是规范化的,如同所有其他常量对象的创建。

assert(Symbol("foo") == Symbol("foo"));
assert(identical(const Symbol("foo"), const Symbol("foo")));

如果name是一个不以下划线开头的单一标识符,或者是一个限定的标识符,或者是一个不同于unary-的操作符名称,那么const Symbol(name)的结果就是通过在name的内容前缀#而创建的符号字面将评估为同一个实例。

assert(Symbol("foo") == #foo);
assert(Symbol("[]=") == #[]=]);
assert(Symbol("foo.bar") == #foo.bar);
assert(identical(const Symbol("foo"), #foo));
assert(identical(const Symbol("[]="), #[]=));
assert(identical(const Symbol("foo.bar"), #foo.bar));

这个构造函数不能创建一个等同于私有符号字面的Symbol实例,比如#_foo。

const Symbol("_foo") // Invalid

创建的实例会覆盖Object.==。

下面的文字是非规范性的。

创建非const的Symbol实例可能会导致更大的输出。如果可能的话,使用 "dart:mirrors "中的MirrorsUsed来指定哪些名字可能被传递给这个构造函数。

参考