The original address: www.dartcn.com/articles/se…

Original author:

Release Date: November 2012 (Updated November 2013)

Note: This article only applies to standalone VMS under the 1.x Dart SDK. We do not recommend using mirrors in web applications, and the Flutter SDK does not support the Dart: Mirrors library.

Reflection in Dart is based on the concept of mirrors, which are simply objects that reflect other objects. In a mirror-based API, whenever people want to reflect on an entity, they have to get a separate object, called a mirror.

Mirror-based reflective apis offer significant advantages in terms of security, distribution, and deployment. On the other hand, using them can sometimes be more cumbersome than the old methods.

For a comprehensive introduction to the principles of mirror-based reflection, see the references at the end of this article. However, you don’t need to delve into this if you don’t want to; what you really need to know about Dart’s mirroring API is covered here.

Note 1: Dart’s mirroring API is constantly evolving; While most of the Introspection API is stable, there will be some additions and tweaks in the future, even after 1.0.

So far, only some of the planned apis have been implemented. The existing section is about introspection, the ability of a program to discover and use its own structure. The Introspection API is basically implemented on the Dart virtual machine.

The introspection API is declared in a library called DART: Mirrors. If you want to use introspection, import it.

import 'dart:mirrors';
Copy the code

For the sake of illustration, let’s assume that you have defined the following class.

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

  MyClass(this.i, this.j);

  static noise() => 42;

  static var s;
}
Copy the code

The easiest way to get a mirror image is to call the top-level function Reflect ().

Note 2: Currently, reflection only works if the reflected code and the reflected object are running in the same isolation zone. In the future, we hope to extend the API to support reflection across quarantines.

The Reflect () method takes an object and returns an InstanceMirror on it.

InstanceMirror myClassInstanceMirror = reflect(new MyClass(3.4));
Copy the code

InstanceMirror is a subclass of Mirror and is the root of the Mirror hierarchy. An InstanceMirror allows people to call dynamically selected code on an object.

InstanceMirror f = myClassInstanceMirror.invoke(#sum, []);
// Returns an InstanceMirror on 7.
Copy the code

The invoke() method receives a symbol representing the method name (in this case, #sum), a list of positional arguments, and, optionally, a mapping describing the named arguments.

Why doesn’t Invoke () accept a string representing the name of a method? Because minimize. Minimization is the process of processing names in web applications in order to reduce downloads.

Symbols were introduced into Dart to help reflection work with minimal effort. The biggest advantage of symbols is that when the Dart program is minimized, the symbols are also minimized. For this reason, the mirror API uses symbols instead of strings. You can convert between symbols and strings; Typically, you do this to print out the name of the declaration, as we’ll see below.

Suppose you want to print out all the declarations in a class. You’ll need a ClassMirror, which, as you might expect, reflects a class. One way to get a class image is from an instance image.

ClassMirror MyClassMirror = myClassInstanceMirror.type; // Reflects MyClass
Copy the code

Another approach is to use the top-level function reflectClass().

ClassMirror cm = reflectClass(MyClass); // Reflects MyClass
Copy the code

Once we have a class image CM in one way or another, we can print out all the declared names of the classes that cm reflects.

for (var m in cm.declarations.values) print(MirrorSystem.getName(m.simpleName));
Copy the code

ClassMirror has a getter declarations that returns a map from the declaration names of the reflected classes to the mirror of those declarations. The map contains all the declarations explicitly listed in the class’s source code: its fields and methods (including getters, setters, and regular methods), whether they are static or not, and all the bar constructors. The map does not contain any inherited members, nor does it contain any synthesized members, such as getters and setters, which are automatically generated for fields.

We extract values from the map; Each value will be a mirror image of a declaration of MyClass and supports a getter simpleName that returns the declaration name. The name returned is a symbol, so we have to convert it to a string for printing. The static method mirrorSystem.getName does this for us.

Obviously, in this case, we know what the declaration in MyClass is; The point is that the for loop above works for a mirror image of any class, so we can use it to print declarations of any class.

printAllDeclarationsOf(ClassMirror cm) {
  for (var m in cm.declarations.values) print(MirrorSystem.getName(m.simpleName));
}
Copy the code

Some methods in the mirroring API return maps in a similar way. These maps allow you to find members by name, walk through all names, or walk through all members. In fact, there’s an easier way to do what we just did.

printAllDeclarationsOf(ClassMirror cm) {
  for (var k in cm.declarations.keys) print(MirrorSystem.getName(k));
}
Copy the code

What if we want to call static code reflexively? We can also call invoke() on ClassMirror.

cm.invoke(#noise, []); // Returns an InstanceMirror on 42
Copy the code

In fact, Invoke () is defined in the ObjectMirror class, a superclass of common mirror classes that reflects Dart entities with stateful and executable code, such as regular instances, classes, libraries, and so on.

Here’s a complete example of what we’ve done so far.

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:');
  可迭代<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);
}
Copy the code

Here is the output.

sum = 7
noise = InstanceMirror on 42

Methods:
my_method

sum

noise

All declarations:
i
j
s
my_method

sum
noise
MyClass
91
Copy the code

At this point, we’ve shown you enough to get started. Here are some other things you should be aware of.

Note 3: You tend to deploy less than you write. This can interact with reflection in annoying ways.

Because the size of the network application needs to be controlled, the deployed Dart application may be minimized and tree-sloshing. We talked about minimization above; Tree shaking is the elimination of uncalled source code. Neither of these steps generally detects reflective use of the code.

This optimization is a fact of life in Dart because of the need to deploy to JavaScript. We need to avoid downloading the entire Dart platform on every web page written with Dart. Tree Shaking does this by detecting the method names actually called in the source code. However, code called based on dynamically computed symbols cannot be detected in this way and is therefore obsolete.

The above situation means that the actual code that exists at run time may be different from the code that you developed. Your code that only uses reflection may not be deployed. Runtime reflection only knows what actually exists in the running program at runtime. This can lead to accidents. For example, one might try to call a method that exists in the source code reflectively, but is optimized because there are no non-reflective calls. Such a call would result in a call to noSuchMethod(). Tree shaking also has an effect on structural reflection. Also, what members of a library or type are at run time may differ from what is in the source code.

In the case of mirroring, people can choose to be more conservative. Unfortunately, because one can get mirrors for any object in the application, all the code in the application must be preserved, including the Dart platform itself. Instead, we can choose to treat this call as if it never existed in the source code.

We are experimenting with mechanisms that let programmers specify that some code may not be eliminated by tree shaking. Currently, you can use the MirrorsUsed annotation to do this, but we expect the details to change a lot over time.

Note 4: What we can assure you is that MirrorsUsed will change. If you use it, be prepared for sudden change.

That should be enough to get you started with mirroring. There’s a lot more to the Introspection API; You can explore the API and see what else there is.

We hope to support more powerful reflection in the future. These features include a mirror builder, designed to allow programs to extend and modify themselves, and a mirror-based debugging API.

reference

Gilad Bracha and David Ungar. mirror image. Design principles for meta-level facilities in object-oriented programming languages. ACM Conference on Object-oriented Programming, Systems, Languages, and Applications, October 2004.

Gilad Bracha. Linguistic Reflections through the Looking-Glass. Screen shot of his talk at HPI Potsdam, January 2010. 57 minutes.

These mirror posts may also prove useful (and take less time to digest).

  • Gilad Bracha. Through the dark telescope.
  • Allen Wirfs-Brock. Experiment with JavaScript mirroring.
  • Gilad Bracha. Seeks closure in the mirror.

Translation via www.DeepL.com/Translator (free version)