【问题标题】:How to implement dynamic properties in Dart?如何在 Dart 中实现动态属性?
【发布时间】:2013-05-02 03:32:08
【问题描述】:

我希望能够使用 noSuchMethod() 中的查找来支持带有 Map 的动态属性。但是,最新的更改使传入的属性引用名称不可用。我可以理解要求我们使用符号而不是字符串作为名称的缩小方案,但这使得实现可序列化的动态属性变得困难。有人对如何解决这个问题有好的想法吗?

  • 我不能使用字符串名称,因为字符串名称在对压缩程序的调用之间不是固定的。 (这将完全破坏序列化)

【问题讨论】:

    标签: dart


    【解决方案1】:

    您可以使用MirrorSystem.getName(symbol)访问原始名称

    所以一个动态类可能看起来像:

    import 'dart:mirrors';
    
    class A {
      final _properties = new Map<String, Object>();
    
      noSuchMethod(Invocation invocation) {
        if (invocation.isAccessor) {
          final realName = MirrorSystem.getName(invocation.memberName);
          if (invocation.isSetter) {
            // for setter realname looks like "prop=" so we remove the "="
            final name = realName.substring(0, realName.length - 1);
            _properties[name] = invocation.positionalArguments.first;
            return;
          } else {
            return _properties[realName];
          }
        }
        return super.noSuchMethod(invocation);
      }
    }
    
    main() {
      final a = new A();
      a.i = 151;
      print(a.i); // print 151
      a.someMethod(); // throws
    }
    

    【讨论】:

    • 实名格式是不是相当稳定,还是每次有新版本的 dart 出来我都得担心更新我的代码?
    • 我真的不知道是否指定了格式,但在 language specification 中有对 setter v= 的引用。所以我会说它几乎被定义了。随时在邮件列表或其他 SO 问题中提问 :)
    • 这段代码无法运行。我收到了The setter 'i' isn't defined for the class 'A'. 错误。
    【解决方案2】:

    你可以这样做:

    import 'dart:json' as json;
    
    main() {
        var t = new Thingy();
        print(t.bob());
        print(t.jim());
        print(json.stringify(t));
    }
    
    class Thingy {
        Thingy() {
            _map[const Symbol('bob')] = "blah";
            _map[const Symbol('jim')] = "oi";
        }
    
        final Map<Symbol, String> _map = new Map<Symbol, String>();
    
        noSuchMethod(Invocation invocation) {
            return _map[invocation.memberName];
        }
    
        toJson() => {
            'bob': _map[const Symbol('bob')],
            'jim': _map[const Symbol('jim')]};
    }
    

    更新 - 动态示例:

    import 'dart:json' as json;
    
    main() {
        var t = new Thingy();
        t.add('bob', 'blah');
        t.add('jim', 42);
        print(t.bob());
        print(t.jim());
        print(json.stringify(t));
    }
    
    class Thingy {
        final Map<Symbol, String> _keys = new Map<Symbol, String>();
        final Map<Symbol, dynamic> _values = new Map<Symbol, dynamic>();
    
        add(String key, dynamic value) {
            _keys[new Symbol(key)] = key;
            _values[new Symbol(key)] = value;
        }
    
        noSuchMethod(Invocation invocation) {
            return _values[invocation.memberName];
        }
    
        toJson() {
            var map = new Map<String, dynamic>();
            _keys.forEach((symbol, name) => map[name] = _values[symbol]);
            return map;
        }
    }
    

    【讨论】:

    • 我真正想要的是类似于 Expando 对象的东西。我希望能够使用固定属性 + 可以在运行时添加的其他属性来定义 Thingy。
    • 我添加了一个动态示例 - 你是这个意思吗?这样做的好处是您不必使用反射,这意味着它可以更好地与 dart2js 一起使用。处理 setter 也应该很容易,而不是使用 add - 你可以使用上面 Alexadres 示例中的一些代码。
    • 糟糕——你显然不能在没有 mirror.GetName() 的情况下处理 setter。
    • @GregLowe 如何抑制these等“未找到方法”错误?
    • 啊:同时,在 Dart 中已经变成了forbidden 来调用没有明确声明的对象的方法。
    【解决方案3】:

    如果你只需要“动态属性”,在 Map 中使用 Symbols 作为键应该就足够了。如果您还想序列化该映射,则需要跟踪原始字符串名称并将其用于序列化。反序列化时,您必须从这些字符串创建新符号。

    请注意,所有这些场景(以及基本上涉及 new Symbol 的所有内容)都需要编译器创建原始名称到缩小名称的映射,并将此映射放入程序中,这当然会使其更大。

    【讨论】:

    • 是的,问题是如何访问原始字符串名称? noSuchMethod 不暴露原始字符串,你从 Invocation 对象得到的只是 Symbol。
    • 嗯,你在把东西放进地图的时候,你必须记录下原来的名字。编辑:哦,是的,接受的答案是正确的,有一个向后映射。不记得了。
    【解决方案4】:

    感谢@Alexandre Ardhuin 的解决方案,我做了一些修改使其可以运行。

    import 'dart:mirrors';
    
    class object {
      final _properties = new Map<String, Object>();
    
      object();
    
      object.from(Map<String, Object> initial) {
        initial.entries.forEach((element) => _properties[element.key] = element.value);
      }
    
      noSuchMethod(Invocation invocation) {
        if (invocation.isAccessor) {
          final realName = MirrorSystem.getName(invocation.memberName);
          if (invocation.isSetter) {
            // for setter realname looks like "prop=" so we remove the "="
            final name = realName.substring(0, realName.length - 1);
            _properties[name] = invocation.positionalArguments.first;
            return;
          } else {
            return _properties[realName];
          }
        }
        return super.noSuchMethod(invocation);
      }
    
      @override
      String toString() {
        return _properties.toString();
      }
    }
    
    main() {
      // we can't use var or object type here, because analysis will consider
      // https://dart.dev/tools/diagnostic-messages#undefined_setter
      // The setter 'i' isn't defined for the type 'object'
      // So dynamic is required here!
      dynamic a = object.from({'a': 123, 'b': 234});
      a.i = 151;
      print(a); // print {a: 123, b: 234, i: 151}
      try {
        a.someMethod(); // throws NoSuchMethodError
      } catch (e) {
        print(e);
      }
    }
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-29
      • 2021-07-28
      • 1970-01-01
      • 2013-02-27
      • 2020-07-06
      • 2012-12-19
      • 1970-01-01
      相关资源
      最近更新 更多