This code does not work on the Dart VM (1.22.0-dev.9.0), but does work on DartPad (unknown version):
import 'dart:mirrors';
class Thing {
Thing();
}
void g(ClassMirror c) {
var constructors = c.declarations.values
.where((d) => d is MethodMirror && d.isConstructor) as Iterable<MethodMirror>;
print(constructors);
}
void main() {
g(reflectClass(Thing));
}
Results in:
Unhandled exception:
type 'WhereIterable<DeclarationMirror>' is not a subtype of type 'Iterable<MethodMirror>' in type cast where
WhereIterable is from dart:_internal
DeclarationMirror is from dart:mirrors
Iterable is from dart:core
MethodMirror is from dart:mirrors
#0 Object._as (dart:core-patch/object_patch.dart:76)
#1 g (file:///google/src/cloud/srawlins/strong/google3/b.dart:9:55)
#2 main (file:///google/src/cloud/srawlins/strong/google3/b.dart:14:3)
#3 _startIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:261)
#4 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:148)
(but in DartPad results in (MethodMirror on 'Thing').)
Note that if I hand-craft some classes that implement each other, and do the same thing, it works:
abstract class DM {
bool get t;
}
abstract class MM implements DM {
MM();
bool get t;
}
class _MM implements MM {
bool get t => true;
}
void f(Map<dynamic, DM> dms) {
var mms = dms.values.where((dm) => dm is MM && dm.t) as Iterable<MM>;
print(mms);
}
void main() {
f({1: new _MM()});
}
which nicely prints: (Instance of '_MM')
Just because the iterable returned by .where() can only contain MethodMirror instances, doesn't allow the cast. The type is propagated from c.declarations.values which is DeclarationMirror.
While you can cast a DeclarationMirror to a MethodMirror, a cast from Iterable<DeclarationMirror> to Iterable<MethodMirror> is invalid, because there is no is-a relationship between these to iterables.
It seems when built to JS by dart2js, some generic types are dropped, this is why this works in DartPad.
You can create a new List<MethodMirror> like
import 'dart:mirrors';
class Thing {
Thing();
}
void g(ClassMirror c) {
var constructors = new List<MethodMirror>.from(
c.declarations.values.where((d) => d is MethodMirror && d.isConstructor));
print(constructors);
}
void main() {
g(reflectClass(Thing));
}
There is an open issue to make this easier https://github.com/dart-lang/sdk/issues/27489
Related
I have a class (with more properties than the example, of course).
How to write the following simple test? I know that equality in Dart is by object instance. How to compare the full object state? I tested with the same matcher as well, with no luck.
import 'package:test/test.dart';
class UnderTesting {
var a;
var b;
UnderTesting({this.a, this.b});
}
void main() {
test("compare objects", () {
final obj1 = UnderTesting(a:1, b:2);
final obj2 = UnderTesting(a:1, b:2);
// Next will fail because it is checking if it is the same instance
expect(obj1, equals(obj2));
} );
}
You need to override the == operator (and should therefore also override hashCode) for UnderTesting. The documentation for equals tells us how to does test for equality:
If [expected] is a [Matcher], then it matches using that. Otherwise it tests for equality using == on the expected value.
So you code should be something like this:
import 'package:test/test.dart';
import 'package:quiver/core.dart';
class UnderTesting {
int a;
int b;
UnderTesting({this.a, this.b});
#override
bool operator ==(Object other) =>
(other is UnderTesting) ? (a == other.a && b == other.b) : false;
#override
int get hashCode => hash2(a, b);
}
void main() {
test("compare objects", () {
final obj1 = UnderTesting(a: 1, b: 2);
final obj2 = UnderTesting(a: 1, b: 2);
expect(obj1, equals(obj2)); // All tests passed!
});
}
I can recommend the quiver package for making easy hashCode implementations: https://pub.dev/documentation/quiver/latest/quiver.core/quiver.core-library.html
With the below code as an example I can not figure out how to make the generic typed Function work with out casting as shown. Every other way I try I get some variation of
The argument type 'Null Function(Gift)' can't be assigned to the
parameter type 'dynamic Function(T)'
var present = Present<Gift>(Gift('Fancy Gift'), <T>(Gift t) {
print('${(t as Gift).name} was opened.');
});
or
The getter 'name' isn't defined for the type 'Object'
var present = Present<Gift>(Gift('Fancy Gift'), <Gift>(t) {
print('${t.name} was opened.');
});
Here is the working example with a cast.
void main() {
var present = Present<Gift>(Gift('Fancy Gift'), <T>(t) {
print('${(t as Gift).name} was opened.');
});
present.open();
}
class Present<T> {
final T _item;
final Function<T>(T t) openedCallback;
T open() {
openedCallback.call(_item);
return _item;
}
Present(this._item, this.openedCallback);
}
class Gift {
final String name;
Gift(this.name);
}
There should be a way to do this without a cast right?
Your class definition does not do what you intend:
class Present<T> {
final T _item;
final Function<T>(T t) openedCallback;
...
openedCallback is separately parameterized; its T type parameter is separate and independent from that of Present<T>. There is no need to parameterize openedCallback since you presumably want:
class Present<T> {
final T _item;
final Function(T t) openedCallback;
...
After that, you can do:
var present = Present<Gift>(Gift('Fancy Gift'), (t) {
print('${t.name} was opened.');
});
Note that doing <T>(t) { ... } or <Gift>(t) { ... } is counterproductive. That declares an anonymous function that itself is generic and is has a type parameter named T or Gift respectively.
I am trying to get callable classes to work in dart, but I have ran into a few issues. First thing I realized is that a normal function
myFunc() {
return 'myFunc';
}
Function.apply(myFunc,null);
is not working as a callable.
Then I realized that if I do
final myFunc = () => 'myFunc';
Function.apply(myFunc,null);
this works.
So now I am trying it out with classes
class Cars {
call(Map<Symbol,dynamic> args) {
return "ride";
}
const Cars();
}
final cars = Cars();
final jobs = {cars.hashCode :cars};
void main() {
int code = cars.hashCode;
print(Function.apply(jobs[code],null));
}
but in DartPad I get the following error
Uncaught exception:
NoSuchMethodError: method not found: 'call'
Receiver: Closure 'call$1' of Instance of 'Cars'
Arguments: []
are there some restrictions on the call method? Or how it works with Function.apply() that I am not finding in the docs?
Thanks.
Your first example works fine for me, but your program needs an entry point:
myFunc() {
return 'myFunc';
}
void main() {
print(Function.apply(myFunc, null));
}
In your class example, your call method requires a Map, but you're passing null. There is no call method with zero arguments, hence the method not found: 'call' error.
One way to fix it is by adding an empty Map to the parameter list in Function.apply:
class Cars {
call(Map<Symbol,dynamic> args) {
return "ride";
}
const Cars();
}
final cars = Cars();
final jobs = {cars.hashCode :cars};
void main() {
int code = cars.hashCode;
print(Function.apply(jobs[code], [Map<Symbol,dynamic>()]));
}
It's worth noting that you can call any method on a class with any number of arguments:
class Car {
go(int speed, int wheels) {
print('$speed mph, $wheels wheels');
}
}
void main() {
var car = Car();
Function.apply(car.go, [50, 4]);
}
class A {
void hello() {
print('world');
}
}
class B extends A {
#override
void hello() {
print('StackOverflow');
}
}
(A() as B).hello(); results in type 'A' is not a subtype of type 'B' in type cast.
Full disclosure: I don't know Dart.
You can't do this kind of cast because it might result in function calls or field accesses that aren't well defined.
I'll change your example to demonstrate:
class A {
void hello() {
print('world');
}
}
class B extends A {
#override
void hello() {
print('StackOverflow');
}
void hello2() {
print('Is Great!');
}
}
Now if you do (A() as B).hello2(); what should Dart do? This isn't really obvious, so it doesn't allow you to do that. Going the other way isn't an issue since B inherits all stuff from A.
class A {
void hello() {
print('world');
}
}
class B extends A {
#override
void hello() {
print('StackOverflow');
}
}
class C extends A {
#override
void hello() {
print('Hello');
}
}
Another issue is a value that has type A may be a different subtype of A like C
The way casting works is you can only go from a more specific type, B in this case, to a more general type, A. Your creating an instance of A, but A is not B.
Dart generally allows you to down-cast from a super-type to a sub-type because the value might actually be of the sub-type.
Animal animal = Cat();
if (something) animal = Dog();
...
Cat cat = animal as Cat; // Allowed, the animal may be a cat.
However, Dart 2 disallows down-casts in a few cases where it is obvious (even to the compiler) that the cast will always fail at run-time. That's what you are hitting here: (A() as B). The A() calls a generative constructor, so the compiler knows that the type of that expressions is exactly A (and not any proper sub-type of A). So, it knows that casting that to B will always fail at run-time, and for your own protection, it disallows the program entirely. Hence, a compile-time error.
I was wondering if is possible to create an instance of a generic type in Dart. In other languages like Java you could work around this using reflection, but I'm not sure if this is possible in Dart.
I have this class:
class GenericController <T extends RequestHandler> {
void processRequest() {
T t = new T(); // ERROR
}
}
I tried mezonis approach with the Activator and it works. But it is an expensive approach as it uses mirrors, which requires you to use "mirrorsUsed" if you don't want to have a 2-4MB js file.
This morning I had the idea to use a generic typedef as generator and thus get rid of reflection:
You define a method type like this: (Add params if necessary)
typedef S ItemCreator<S>();
or even better:
typedef ItemCreator<S> = S Function();
Then in the class that needs to create the new instances:
class PagedListData<T>{
...
ItemCreator<T> creator;
PagedListData(ItemCreator<T> this.creator) {
}
void performMagic() {
T item = creator();
...
}
}
Then you can instantiate the PagedList like this:
PagedListData<UserListItem> users
= new PagedListData<UserListItem>(()=> new UserListItem());
You don't lose the advantage of using generic because at declaration time you need to provide the target class anyway, so defining the creator method doesn't hurt.
You can use similar code:
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'.");
}
}
}
I don't know if this is still useful to anyone. But I have found an easy workaround. In the function you want to initialize the type T, pass an extra argument of type T Function(). This function should return an instance of T. Now whenever you want to create object of T, call the function.
class foo<T> {
void foo(T Function() creator) {
final t = creator();
// use t
}
}
P.S. inspired by Patrick's answer
2022 answer
Just came across this problem and found out that although instantiating using T() is still not possible, you can get the constructor of an object easier with SomeClass.new in dart>=2.15.
So what you could do is:
class MyClass<T> {
final T Function() creator;
MyClass(this.creator);
T getGenericInstance() {
return creator();
}
}
and when using it:
final myClass = MyClass<SomeOtherClass>(SomeOtherClass.new)
Nothing different but looks cleaner imo.
Here's my work around for this sad limitation
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();
}
Sorry but as far as I know, a type parameter cannot be used to name a constructor in an instance creation expression in Dart.
Working with FLutter
typedef S ItemCreator<S>();
mixin SharedExtension<T> {
T getSPData(ItemCreator<T> creator) async {
return creator();
}
}
Abc a = sharedObj.getSPData(()=> Abc());
P.S. inspired by Patrick
simple like that.
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});
}
Inspired by Patrick's answer, this is the factory I ended up with.
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());
}
}
Then I would use it like this.
final authClient = ServiceFactory.getInstance<AuthenticationClient>(() => AuthenticationClient());
Warning: Erik made a very good point in the comment below that the same type name can exist in multiple packages and that will cause issues. As much as I dislike to force the user to pass in a string key (that way it's the consumer's responsibility to ensuring the uniqueness of the type name), that might be the only way.