EggBender
EggBender

Reputation: 1020

How to enforce Implements constraint on generic type T in generic Dart method?

What I'm trying to achieve

I am trying to create a generic Dart method that takes a generic type T where T has to implement class IModel. However,

Problem

It seems like I can't set an implements constraint on type T, only an extends. For example <T implements IModel does not work but <T extends IModel> works.

Code

IModel.dart which serves as an interface:

class IModel {
  IModel();

  factory IModel.fromJson(Map<String, dynamic> json) {
    return IModel();
  }

  Map<String, dynamic> toJson() => {};

  IModel clone() {
    return IModel();
  }
}

MyModel.dart, an example that implements IModel:

import 'dart:core';
import 'package:.../helpers/DateTimeHelper.dart';
import 'package:.../helpers/StringHelper.dart';
import 'package:.../models/IModel.dart';

class MyModel implements IModel {
  String id;
  String name;
  String companyId;
  String projectId;
  String description;
  DateTime created;
  DateTime updated;

  MyModel({this.id, this.name, this.description, this.created, this.updated, this.companyId, this.projectId});

  factory MyModel.fromJson(Map<String, dynamic> json) {
    return MyModel(id: json["id"], name: json["name"], created: DateTime.parse(json["created"]), updated: DateTime.parse(json["updated"]), description: json["description"], companyId: json["companyId"], projectId: json["projectId"]);
  }

  Map<String, dynamic> toJson() => {
        "Id": id,
        "Name": name,
        "Description": description,
        "CompanyId": companyId,
        "ProjectId": projectId,
        "Created": created.toIso8601String(),
        "Updated": updated.toIso8601String()
      };

  MyModel clone() {
    return new MyModel(id: StringHelper.clone(id), companyId: StringHelper.clone(companyId), created: DateTimeHelper.clone(created), updated: DateTimeHelper.clone(updated), name: StringHelper.clone(name), projectId: StringHelper.clone(projectId), description: StringHelper.clone(description));
  }
}

ModelHelper.dart with the generic method:

import 'dart:convert';

import 'package:.../models/IModel.dart';

// 'T implements IModel' not working, but 'T extends IModel' does as constraint.
abstract class ModelHelper {
  static List<T> listFromJson<T implements IModel>(String json) {
    List l = jsonDecode(json)["data"];
    var models = l.map((m) => T.fromJson(m)).toList();
    return models;
  }
}

Additional questions/conclusion

Are implements constraints not supported in Dart? Do all T classes simply have to extend IModel?

Upvotes: 6

Views: 3425

Answers (2)

jamesdlin
jamesdlin

Reputation: 89956

  1. There is no way to specify an implements constraint for a generic type argument, and such a constraint should never be necessary. Given:

    class Base {}
    class Derived1 extends Base {}
    class Derived2 implements Base {}
    
    void genericFunction<T extends Base>() {}
    

    the generic type constraint T extends Base means that the type argument derives from Base. It does not (and should not) require that the type argument uses the extends keyword. Whether the type argument uses extends or implements is an implementation detail of that class (Derived1 or Derived2 in this example) and should be of no concern to the generic (genericFunction) itself. Instances of Derived1 and of Derived2 both provide the same interface as Base regardless of whether they use extends or implements, and that's all that matters.

  2. Even if there were an implements constraint for generic type arguments, it wouldn't do what you want. You have the misunderstanding that Derived implements Base will provide the same constructors or static methods as Base. In Dart, constructors and static methods are not part of the class's interface. They are not inheritable. For example:

    class Base {
      Base.namedConstructor();
    
      static void staticMethod() {}
    }
    
    class Derived implements Base {} // Perfectly legal.
    
    void main() {
      var d = Derived(); // Legal.
      d = Derived.namedConstructor(); // ERROR
      Derived.staticMethod(); // ERROR
    }
    

    There is no way for a generic to require that a type argument have a particular constructor or static method and therefore there is no way to invoke constructors or static methods on type arguments. In general, you instead would need to pass a callback that invokes the desired constructor or static method. Specifically for serializing/deserializing JSON, you should use an existing package that utilizes code-generation (such as package:json_serializable or package:built_value) instead of trying to implement it yourself.

Also see:

Upvotes: 0

R&#233;mi Rousselet
R&#233;mi Rousselet

Reputation: 276997

Problem

It seems like I can't set an implements constraint on type T, only an extends. For example <T implements IModel does not work but works.

This is not a problem. You can safely use <T extends IModel>, even for classes that implements IModel:

abstract class Abstract {}

class Concrete implements Abstract {}

void function<T extends Abstract>(T value) {
  print(value);
}

void main() {
  function<Concrete>(Concrete()); // works
}

Upvotes: 11

Related Questions