Using reflection

The Dart mirror-based reflection API (contained in the dart:mirrors library) provides a powerful set of tools to reflect on code. This means that it is possible to introspect the complete structure of a program and discover all the properties of all the objects. In this way, methods can be invoked reflectively. It will even become possible to dynamically evaluate code that was not yet specified literally in the source code. An example of this would be calling a method whose name was provided as an argument because it is looked up in the database table.

Getting ready

The part of your code that uses reflection should have the following import code:

import 'dart:mirrors';

How to do it...

To perform reflection we perform the following actions:

  • In the reflection project, we use a class Embrace to reflect upon:
    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:
  • Use of MirrorSystem, as shown in the following code:
        final MirrorSystem ms = currentMirrorSystem();
         // Iterating through libraries
        ms
           .libraries
           .forEach((Uri name, LibraryMirror libMirror){
              print('$name $libMirror'),
           });
  • Use of InstanceMirror and ClassMirror, as shown in the following code:
        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; }
         }
       }
    }
  • Running the previous program produces the following output:

    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

How it works...

The currentMirrorSystem class returns a MirrorSystem object on the current isolate; the libraries getter gives you the list of libraries in the scope of the current code.

The InstanceMirror subclass is a representation of an instance of an object and ClassMirror is the representation of the class definition.

Use the top-level reflect method on an object to get InstanceMirror. This allows you to dynamically invoke code on the object producing another InstanceMirror; using its reflectee property gives you access to the actual instance.

Note that the invoke method takes as its first argument a symbol (recognizable from its # prefix) for the method name. Symbols were introduced in Dart because they survive minification.

The top-level reflectClass method on a class results in ClassMirror; the same type of object is given by calling type on InstanceMirror of that class. The ClassMirror class has a declarations getter that returns a map from the names of the declarations to the mirrors on them. Static methods can be called on ClassMirror.

For every type of object in Dart, there exists a corresponding mirror object. So we have Variabl eMirror, MethodMirror, ClassMirror, LibraryMirror, and so on. Invoking newInstance on a ClassMirror class with the name of a constructor as a symbol produces InstanceMirror; you can then call methods on the real object via reflectee.

There's more...

There are some things we should be aware of when using reflection:

  • The mirror API is still evolving, so expect some additions and adjustments in the future. The implementation is most complete for code running in the Dart VM.
  • Mirroring in dart2js lags a bit behind. The processes of minifying and tree shaking your app performed by dart2js will generally not detect the reflected code. So the use of reflection at runtime might fail, resulting in noSuchMethod() errors. To prevent this from happening, use the Mirrors annotation, as shown in the following code, which helps the dart2js compiler to generate a smaller code:
    @MirrorsUsed(override:'*')
    import 'dart:mirrors';
  • One of the restrictions is the reflections across isolates. At the time of writing this book, reflection only works if the reflection code and the object being reflected are running in the same isolate.
  • Suppose you have an undocumented method that returns a Future value and you want to know the properties and methods of that object without digging into the source code. Run the following code snippet:
    import 'dart:mirrors';
    
    undocumentedMethod().then((unknown){
       	var r = reflect(unknown).type;  // ClassMirror
          var m = r.declarations;
    for (var k in m.declarations.keys) print(MirrorSystem.getName(k));
    }); 

See also

  • When you want to use reflection in the code, which has to be minified and tree shaken, read the Shrinking the size of your app recipe in Chapter 1, Working with Dart Tools
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset