简体   繁体   English

如何使用 generics 与 Dart 中的组合?

[英]How to use generics with composition in Dart?

I'm having issues with generics types in Dart. Take the following entities example in consideration:我在 Dart 中遇到 generics 类型的问题。请考虑以下实体示例:

enum ProductType { book }

abstract class ProductMetadata {
  ProductMetadata(this.type);
  
  final ProductType type;
}

class BookProductMetadata extends ProductMetadata {
  BookProductMetadata(this.pages) : super(ProductType.book);
  
  final int pages;
}

abstract class Product<M> {
  Product(this.metadata);
  
  final M metadata;
}

class BookProduct extends Product<BookProductMetadata> {
  BookProduct(BookProductMetadata metadata) : super(metadata);
}

Where we would have multiple different kinds of product with different kinds of metadata .我们会有多种不同类型的product和不同类型的metadata I'm trying to build a serializer that support this structure, and I stuck with the following:我正在尝试构建一个支持此结构的序列化程序,但我坚持使用以下内容:

class ProductSerializer {
  Product<M> from<M extends ProductMetadata>(Map<String, dynamic> json) {
    final metadata = json['metadata'] as Map<String, dynamic>;
    final type = metadata['type'] as ProductType;
    
    ProductMetadata formattedMetadata;
    
    switch (type) {
      case ProductType.book:
        formattedMetadata = BookProductMetadata(123);
    }
    
    return BookProduct(metadata: formattedMetadata);
    }
}

Which gives me the error A value of type 'BookProduct' can't be returned from the method 'from' because it has a return type of 'Product<M>' .这给了我错误A value of type 'BookProduct' can't be returned from the method 'from' because it has a return type of 'Product<M>'

The goal in this serializer was to avoid at most type-casting using as and to use generics for whoever uses this serializer to determine what should be its returning type.此序列化程序的目标是最多避免使用as进行类型转换,并为使用此序列化程序的任何人使用 generics 来确定其返回类型。 I was able to achieve a similar example in a Typescript project, but I am not being able to take full advantage of generics in Dart and I can't understand why, since it seems natural to me that BookProduct<BookMetadata> should be accepted as Product<M> when M extends ProductMetadata .我能够在Typescript项目中实现类似的示例,但我无法在 Dart 中充分利用 generics,我不明白为什么,因为对我来说BookProduct<BookMetadata>应该被接受为Product<M>M extends ProductMetadata时。

You can't avoid casting.你无法避免铸造。

The code代码

    switch (type) {
      case ProductType.book:
        formattedMetadata = BookProductMetadata(123);
    }
    
    return BookProduct(metadata: formattedMetadata);

suggests that your function always returns a BookProduct .建议您的 function 始终返回BookProduct

Which it does, because book is the only existing product type, but I'd expect that to change.它确实如此,因为book是唯一现有的产品类型,但我希望这种情况会发生变化。 If there were more product types, then the switch would not be exhaustive, and the following code would not be able to know that the formattedMetadata is always BookProductMetata .如果有更多的产品类型,那么 switch 就不会穷举,下面的代码就无法知道formattedMetadata总是BookProductMetata

Assume there was also a MovieProductMetadata implementing Metadata , and a MovieProduct implementing Product<MovieProductMetadata> .假设还有一个实现MetadataMovieProductMetadata和一个实现Product<MovieProductMetadata> MovieProduct MovieProduct。 In that case, the static type system sees a function with type Product<M> Function<M extends ProductMetadata>(...) which returns a BookProduct implementing Product<BookProductMetadata> .在这种情况下,static 类型系统会看到类型为Product<M> Function<M extends ProductMetadata>(...)BookProduct ,它返回一个实现Product<BookProductMetadata>的 BookProduct。 It cannot assume that to be valid, because M could be MovieProductMetadata .不能假设它是有效的,因为M可能是MovieProductMetadata (Or, even without those extra types, M could always be Never , it's a subtype of any type.) (或者,即使没有那些额外的类型, M也可以永远是Never ,它是任何类型的子类型。)

If you had more cases, the return should probably be inside the switch case.如果你有更多的情况,返回可能应该在开关箱内。 If it was inside the switch case, you don't need the shared formattedMetadata variable, and could have done:如果它在开关盒内,则不需要共享formattedMetadata变量,并且可以这样做:

    switch (type) {
      case ProductType.book:
        var formattedMetadata = BookProductMetadata(123);
        return BookProduct(metadata: formattedMetadata) as Product<M>;
      case ProductType.movie: ...
    }

You still need the as cast , because static analysis cannot see why a BookProduct should be guaranteed to be a Product<M> for every valid M .您仍然需要as cast ,因为 static 分析看不出为什么BookProduct应该保证是每个有效MProduct<M> It requires the caller to know, ahead of time, which type of product is going to be returned for the JSON they put in. Which is fine, but that still means at least one runtime check, like the as .它要求调用者提前知道他们输入的 JSON 将返回哪种类型的产品。这很好,但这仍然意味着至少需要进行一次运行时检查,例如as

The type of the returned object depends on the value of the metadata type in the incoming JSON. That's dependent typing (types depending on runtime values), and Dart's static type system can't do that.返回的 object 的类型取决于传入的 JSON 中元数据类型的。这是依赖类型(类型取决于运行时值),而 Dart 的 static 类型系统无法做到这一点。 That's why you need runtime type checks too.这就是为什么您也需要运行时类型检查的原因。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM