在DART中创建一个泛型类型的实例

72

我想知道在Dart中是否可以创建泛型类型的实例。在其他语言中,比如Java,你可以使用反射来解决这个问题,但我不确定在Dart中是否可行。

我有这个类:

class GenericController <T extends RequestHandler> {

    void processRequest() {
        T t = new T();  // ERROR
    }
}

1
在Dart中,类型参数实现了接口“Type”。接口“Type”没有声明任何成员。这意味着接口“Type”仅用作运行时类型的标识键。Dart SDK内置了反射过程,但它们不是Dart核心的一部分。这意味着如果您想要检查您的程序,您应该使用反射库。您的程序(在运行时)和反射库之间的桥梁是接口“Type”。您可以使用此接口请求(反射)有关类的所需信息。 - mezoni
1
请参见https://github.com/dart-lang/sdk/issues/12921。 - Günter Zöchbauer
这不是确切的解决方案,但可能适用于您;void processRequest(T t) { t.something(); } - Erdem Siyam
9个回答

103
我尝试了Mezonis的方法,使用Activator可以实现。但这是一种昂贵的方法,因为它使用镜像,如果您不想要一个2-4MB的js文件,则需要使用“mirrorsUsed”。
今天早上我有一个想法,可以使用通用typedef作为生成器,从而摆脱反射:
您可以定义一个方法类型,如下所示:(如有必要,请添加参数)
typedef S ItemCreator<S>();

或者更好的是:

typedef ItemCreator<S> = S Function();

接下来在需要创建新实例的类中:

class PagedListData<T>{
  ...
  ItemCreator<T> creator;
  PagedListData(ItemCreator<T> this.creator) {

  }

  void performMagic() {
      T item = creator();
      ... 
  }
}

然后,您可以像这样实例化PagedList
PagedListData<UserListItem> users 
         = new PagedListData<UserListItem>(()=> new UserListItem());

您使用泛型并不会失去优势,因为在声明时您需要提供目标类,因此定义创建者方法不会有任何影响。


7
与 Flutter 兼容。 - Soul_man
1
它对我完全起作用了。但是我不明白creator()如何创建T的实例而不是ItemCreator<T>的实例。你能为我解释一下吗? - tufekoi
2
ItemCreator<T>是一个函数类型,它返回类型为T的某个东西,正如您在typedef中所看到的那样。在最后的代码片段中,您可以看到创建了PagedListData实例,并提供了一个creator函数的实例。这只是一个模板,而不是一个函数调用。该函数仅在中间代码片段中的"creator()"处被调用。这有帮助吗? - Patrick Cornelissen
7
这里真的没有什么魔法。实际上就是通过回调将实例化责任委托给消费者。这不是一个坏的解决方案,但如果能在 PagedListData 内部动态实例化类型会更好。但我认为在Flutter中没有镜像的情况下这是不可能的。 - DarkNeuron
1
出于个人口味,您可能希望将PagedListData和creator()声明为抽象类,以便它们在子类级别上实现,而不是由客户端实现。 - Anton Duzenko
显示剩余3条评论

15
您可以使用类似的代码:
import "dart:mirrors";

void main() {
  var controller = new GenericController<Foo>();
  controller.processRequest();
}

class GenericController<T extends RequestHandler> {
  void processRequest() {
    //T t = new T();
    T t = Activator.createInstance(T);
    t.tellAboutHimself();
  }
}

class Foo extends RequestHandler {
  void tellAboutHimself() {
    print("Hello, I am 'Foo'");
  }
}

abstract class RequestHandler {
  void tellAboutHimself();
}

class Activator {
  static createInstance(Type type, [Symbol constructor, List
      arguments, Map<Symbol, dynamic> namedArguments]) {
    if (type == null) {
      throw new ArgumentError("type: $type");
    }

    if (constructor == null) {
      constructor = const Symbol("");
    }

    if (arguments == null) {
      arguments = const [];
    }

    var typeMirror = reflectType(type);
    if (typeMirror is ClassMirror) {
      return typeMirror.newInstance(constructor, arguments, 
        namedArguments).reflectee;
    } else {
      throw new ArgumentError("Cannot create the instance of the type '$type'.");
    }
  }
}

7
目前无法在Flutter中使用。支持Patrik下面的方法。 - Soul_man

14

2023答案

除了以下代码外,较新版本的Dart允许您在实例化时甚至不传递泛型类型,代码变得更加简洁:

final myClass = MyClass(SomeOtherClass.new);

感谢 @AndriiSyrokomskyi 指出这个问题。
2022年的答案
刚刚遇到这个问题,发现虽然使用 T() 进行实例化仍然不可能,但是在 dart>=2.15 中可以更容易地获取对象的构造函数,使用 SomeClass.new
所以你可以这样做:
class MyClass<T> {
  final T Function() creator;
  MyClass(this.creator);

  T getGenericInstance() {
    return creator();
  }
}

使用它时:
final myClass = MyClass<SomeOtherClass>(SomeOtherClass.new)

没什么不同,只是看起来更干净而已。

这根本不起作用。没有所谓的.new - Dinesh
1
@Dinesh 我不知道你在说什么,但是直接将代码粘贴到dartpad.dev上就可以运行。 - Allen Hu
抱歉,我的错。看起来有.new但它并没有创建SomeOtherClass的实例。除非我漏掉了什么。 - Dinesh
现在已经可以写得更简短了: final myClass = MyClass(SomeOtherClass.new) - Andrii Syrokomskyi
1
@AndriiSyrokomskyi 你说得对,谢谢通知新的语言变化。我已更新我的回答。 - Allen Hu

11

我不知道这对任何人是否仍然有用。但是我找到了一个简单的解决方法。在你想要初始化类型T的函数中,传递一个额外的参数,类型为T Function()。这个函数应该返回T的一个实例。现在每当你想要创建T对象时,调用这个函数即可。

class foo<T> {
    void foo(T Function() creator) {
        final t = creator();
        // use t
    }
}

P.S. 受到 Patrick的回答 的启发


7
这是我的解决方法,以应对这个悲哀的限制。
class RequestHandler {
  static final _constructors = {
    RequestHandler: () => RequestHandler(),
    RequestHandler2: () => RequestHandler2(),
  };
  static RequestHandler create(Type type) {
    return _constructors[type]();
  }
}

class RequestHandler2 extends RequestHandler {}

class GenericController<T extends RequestHandler> {
  void processRequest() {
    //T t = new T(); // ERROR
    T t = RequestHandler.create(T);
  }
}

test() {
  final controller = GenericController<RequestHandler2>();
  controller.processRequest();
}

4
抱歉,据我所知,在Dart中,类型参数不能用于实例创建表达式中的构造函数命名。

2

使用Flutter开发

typedef S ItemCreator<S>();

mixin SharedExtension<T> {

    T getSPData(ItemCreator<T> creator) async {
        return creator();
    }
}

Abc a = sharedObj.getSPData(()=> Abc());

顺便说一句,这篇文章的灵感来自Patrick


为什么这个函数是 async 的呢? - Hemil

-1
Patrick's answer的启发,这就是我最终得出的工厂。
class ServiceFactory<T> {
  static final Map<Type, dynamic> _cache = <String, dynamic>{};

  static T getInstance<T>(T Function() creator) {
    String typeName = T.toString();
    return _cache.putIfAbsent(typeName, () => creator());
  }
}

然后我会像这样使用它。

final authClient = ServiceFactory.getInstance<AuthenticationClient>(() => AuthenticationClient());

警告:Erik在下面的评论中指出,相同类型名称可能存在于多个包中,这将导致问题。尽管我不喜欢强制用户传递字符串key(这样消费者负责确保类型名称的唯一性),但那可能是唯一的方法。


嗨,詹姆斯,请注意将“Type”实例转换为字符串是容易出错且不必要的(显然,注释不包含代码,请重新格式化):/* Library 'lib.dart'. */ class C {}/* Library 'main.dart'. */ import 'lib.dart' as lib;class C {}void main() { print('${(C).toString()} == ${((lib.C).toString())}'); }重点是两个不同的库可能会声明具有相同名称的类,并且它们将具有相同的“toString”,但它们不是同一个类。 - Erik Ernst
@ErikErnst,你说得很对,我已经编辑了答案并加入了相应的警告,谢谢! - James Poulose
1
谢谢!实际上,您只需要使用 Map<Type, dynamic> _cache_cache.putIfAbsent(T, () => creator()); - Erik Ernst

-1

就是这么简单。

import 'dart:mirrors';

void main(List<String> args) {
    final a = A<B>();
    final b1 = a.getInstance();
    final b2 = a.getInstance();
    print('${b1.value}|${b1.text}|${b1.hashCode}');
    print('${b2.value}|${b2.text}|${b2.hashCode}');
}

class A<T extends B> {
    static int count = 0;
    T getInstance() {
        return reflectClass(T).newInstance(
        Symbol(''),
        ['Text ${++count}'],
        {Symbol('value'): count},
        ).reflectee;
    }
}

class B {
    final int value;
    final String text;
    B(this.text, {required this.value});
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接