Change how map keys are checked for equality - dart

I have the following code:
class KeyClass {
int property;
KeyClass(this.property);
}
void main() {
KeyClass kc1 = KeyClass(1);
KeyClass kc2 = KeyClass(2);
Map<KeyClass, String> map = Map();
map[kc1] = 'hello';
map[kc2] = 'world';
...
}
My goal is to for the following two lines to get the same value from my map:
print(map[kc1]); // prints 'hello'
print(map[KeyClass(1)]); // prints 'null', should print 'hello' too!
Is this possible in Dart language?

The default Map implementation is a LinkedHashMap, so it relies on computing hash codes for the keys. There are a few ways you could make your keys compare equal:
Implement KeyClass.operator == and KeyCode.hashCode:
class KeyClass {
int property;
KeyClass(this.property);
bool operator ==(dynamic other) {
return runtimeType == other.runtimeType && property == other.property;
}
int get hashCode => property.hashCode;
}
Use LinkedHashMap directly. LinkedHashMap's constructor allows providing custom callbacks for computing equality and hash codes:
bool keyEquals(KeyClass k1, KeyClass k2) => k1.property == k2.property;
int keyHashCode(KeyClass k) => k.property.hashCode;
Map<KeyClass, String> map = LinkedHashMap<KeyClass, String>(
equals: keyEquals,
hashCode: keyHashCode,
);

Related

'is' statement with a variable type

I want to check, if my variable k has a type calles T.
My approach was
int k=1;
Type T=int;
if(k is T) print('same type');
But it is not working. It works, if I write
if(k is int)
but I want to change the type in a variable.
Thank you for an answer
You could store the type in a string, and then use runtimeType and toString() to compare the variable's type with the type stored in the string:
int k = 1;
String type = "int";
if (k.runtimeType.toString() == type){
print("k is an integer");
}
You can't do type checks using Type objects in Dart.
A Type object is not the type, it's just a token representing the type which can be used with the dart:mirrorsreflection library. It cannot, really, be used for anything else.
If you need to do type checking, you need to store the type as a type variable, which means you need something generic, or store it in plain code as a closure.
The closure approach is simpler, but less readable:
int k = 1;
var typeChecker = (o) => o is int;
if (typeChecker(o)) print("k has the right type");
Using a generic helper class is more general:
class Typer<T> {
bool isType(Object o) => o is T;
bool operator >=(Typer other) => other is Typer<T>;
bool operator <=(Typer other) => other >= this;
}
...
var k = 1;
var type = Typer<int>();
if (type.isType(k)) print("k is integer");
In short, don't use Type for anything except dart:mirrors because it isn't really useful for anything else.
Some Type in the Dart returns a different kind of Type when using .runtimeType.
For example:
void main() async {
List value = [];
print(value.runtimeType); // print -> JSArray<dynamic>
}
I am using:
void main() async {
List value = [];
print(isSameType(target: value, reference: <Object>[])); // print -> false
value = [Object()];
print(isSameType(target: value, reference: <Object>[])); // print -> false
value = <Object>[];
print(isSameType(target: value, reference: <Object>[])); // print -> true
}
bool isSameType({required target, required reference}) =>
target.runtimeType == reference.runtimeType;
class Object {}
But I saw many comments saying the .runtimeType is for debugging and some comments said it will be not available in the future. So I am using this instead of the code above:
void main() async {
var value;
value = [];
print(value.runtimeType); // print -> JSArray<dynamic>
print(isSameType<List>(value)); // print -> true
value = [Test];
print(value.runtimeType); // print -> JSArray<Type>
print(isSameType<List<Test>>(value)); // print -> false
print(isSameType<List>(value)); // print -> true
value = [Test()];
print(value.runtimeType); // print -> JSArray<Test>
print(isSameType<List<Test>>(value)); // print -> true
print(isSameType<List>(value)); // print -> true
value = <Test>[];
print(value.runtimeType); // print -> JSArray<Test>
print(isSameType<List<Test>>(value)); // print -> true
print(isSameType<List>(value)); // print -> true
}
bool isSameType<type>(target) => target is type;
class Test {}
Basic example for using:
void main() async {
MyObject phoneNumber = MyObject<int>();
phoneNumber = await getDataFromUser();
if (phoneNumber.isSameType()) await uploadData(phoneNumber);
}
class MyObject<type> {
MyObject({this.data});
dynamic data;
bool isSameType() => data is type;
}
Future<dynamic> getDataFromUser() async {
return null;
}
Future<bool> uploadData(data) async {
return false;
}

Test stream with wrapped array

I'm trying to test that the function emitArray emits a Response.Success an its value is ['test'].
If I emit a List<String> everything works as expected, but once I wrap the result list in a Response<List<String>> the test fails.
The result is emitted, but it fails when comparing with the expected result.
I'm wondering if it's related to the implementation of == in Response.Success, I'm using the default implementation that the IDE provides.
This is not the real code I have, it's just a simple example that is easier to understand to try to identify the issue.
This is my class to test:
class ListResponse {
final _array = BehaviorSubject<Response<List<String>>>();
Stream<Response<List<String>>> get array => _array.stream;
Future<void> emitArray() async {
_array.add(Response.success(['test']));
}
void dispose() {
_array.close();
}
}
This is my test:
void main() {
ListResponse underTest;
setUp(() {
underTest = ListResponse();
});
test('It should emit array', () {
final array = Response.success(['test']);
expect(
underTest.array,
emitsInOrder([
array,
emitsDone,
]),
);
underTest.emitArray();
underTest.dispose();
});
}
This is the error it throws:
Expected: should do the following in order:
• emit an event that SuccessResponse<List<String>>:<SuccessResponse{value: [test]}>
• be done
Actual: <Instance of 'BehaviorSubject<Response<List<String>>>'>
Which: emitted • SuccessResponse{value: [test]}
x Stream closed.
which didn't emit an event that SuccessResponse<List<String>>:<SuccessResponse{value: [test]}>
This is the Response class
class Response<T> {
Response._();
factory Response.success(T value) = SuccessResponse<T>;
factory Response.error(Exception error) = ErrorResponse<T>;
}
class ErrorResponse<T> extends Response<T> {
ErrorResponse(this.error): super._();
final Exception error;
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is ErrorResponse &&
runtimeType == other.runtimeType &&
error == other.error;
#override
int get hashCode => error.hashCode;
#override
String toString() {
return 'ErrorResponse{error: $error}';
}
}
class SuccessResponse<T> extends Response<T> {
SuccessResponse(this.value): super._();
final T value;
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is SuccessResponse &&
runtimeType == other.runtimeType &&
value == other.value;
#override
int get hashCode => value.hashCode;
#override
String toString() {
return 'SuccessResponse{value: $value}';
}
}
I'm wondering if it's related to the implementation of == in Response.Success
Exactly. This particular test is failing because you can't compare Lists with ==:
abstract class List<E> implements EfficientLengthIterable<E> {
...
/**
* Whether this list is equal to [other].
*
* Lists are, by default, only equal to themselves.
* Even if [other] is also a list, the equality comparison
* does not compare the elements of the two lists.
*/
bool operator ==(Object other);
}
As a workaround you can change the implementation to compare objects' string representations instead:
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is SuccessResponse &&
runtimeType == other.runtimeType &&
value.toString() == other.value.toString();
Interestingly, passing unwrapped List<String>s objects passes test. That happens because StreamMatcher uses equals() from matcher package to match events, and equals() can match lists and maps. It first tries to match objects with ==, then checks whether they are Iterable/Set/Map (and deep matches them recursively), and then reports mismatch error.

Limit a generic type argument only to be a int, double or custom class

I trying make the following code but T only can be int, double or a custom class. I couldn't find how to restrict the type in Dart or something that work like where from C#. How can I do that in Dart?
class Array3dG<T> extends ListBase<T> {
List<T> l = List<T>();
Array3dG(List<T> list) {
l = list;
}
set length(int newLength) { l.length = newLength; }
int get length => l.length;
T operator [](int index) => l[index];
void operator []=(int index, T value) { l[index] = value; }
}
There is no way to constrain the type variable at compile-time. You can only have one bound on a type variable, and the only bound satisfying both int and your custom class is Object.
As suggested by #Mattia, you can check at run-time and throw in the constructor if the type parameter is not one of the ones you supprt:
Array3dG(this.list) {
if (this is! Array3dG<int> &&
this is! Array3dG<double> &&
this is! Array3dG<MyClass>) {
throw ArgumentError('Unsupported element type $T');
}
}
This prevents creating an instance of something wrong, but doesn't catch it at compile-time.
Another option is to have factory methods instead of constructors:
class Array3dG<T> {
List<T> list;
Array3dG._(this.list);
static Array3dG<int> fromInt(List<int> list) => Array3dG<int>._(list);
static Array3dG<int> fromDouble(List<double> list) => Array3dG<double>._(list);
static Array3dG<MyClass> fromMyClass(List<MyClass> list) => Array3dG<MyClass>._(list);
...
}
which you then use as Array3dG.fromInt(listOfInt). It looks like a named constructor, but it is just a static factory method (so no using new in front).
You can check at runtime the type with the is keyword:
Array3dG(List<T> list) {
if (list is List<int>) {
//Handle int
}
else if (list is List<double>) {
//Handle double
}
else if (list is List<MyClass>) {
//Handle MyClass
}
else {
throw ArgumentError('Unsupported $T type');
}
}
Note that if you are handling int and double in the same way you can just check for num
You can check the progress of the Union types here: https://github.com/dart-lang/sdk/issues/4938

How to set values of global variables used in function parameters

I can conveniently change opsCount variable directly from inside the function,
because there is only one of that type of variable.
int opsCount = 0;
int jobXCount = 0;
int jobYCount = 0;
int jobZCount = 0;
void doStats(var jobCount) {
opsCount++;
jobCount++;
}
main() {
doStats(jobXCount);
}
But there are many jobCount variables, so how can I change effectively that variable, which is used in parameter, when function is called?
I think I know what you are asking. Unfortunately, the answer is "you can't do this unless you are willing to wrap your integers". Numbers are immutable objects, you can't change their value. Even though Dart's numbers are objects, and they are passed by reference, their intrinsic value can't be changed.
See also Is there a way to pass a primitive parameter by reference in Dart?
You can wrap the variables, then you can pass them as reference:
class IntRef {
IntRef(this.val);
int val;
#override
String toString() => val.toString();
}
IntRef opsCount = new IntRef(0);
IntRef jobXCount = new IntRef(0);
IntRef jobYCount = new IntRef(0);
IntRef jobZCount = new IntRef(0);
void doStats(var jobCount) {
opsCount.val++;
jobCount.val++;
}
main() {
doStats(jobXCount);
print('opsCount: $opsCount; jobXCount: $jobXCount; jobYCount: $jobYCount; jobZCount: $jobZCount');
}
EDIT
According to Roberts comment ..
With a custom operator this would look like:
class IntRef {
IntRef(this.val);
int val;
#override
String toString() => val.toString();
operator +(int other) {
val += other;
return this;
}
}
void doStats(var jobCount) {
opsCount++;
jobCount++;
}

What can i do with a stored type?

Dart allows variables of types: Type type = SomeType; But for what purpose?
For example, foo bar baz are misapplications:
class A {
Type type = List;
foo() => new type();
type bar() {
return new List();
}
type baz = new List();
}
void main() {
Type type = String;
var str = "Hello Dart";
print(type == str.runtimeType);//true
print(str is String);//true
print(str is type); //type error.
}
I think this one is pretty neat:
void main() {
foo(Type t) {
switch (t){
case int: return 5;
case List: return [1,2,3]; // This one gets me every time :(
case String: return "Hello Dart!";
default: return "default";
}}
print(foo(10.runtimeType)); //5
print(foo([2,4,6].runtimeType)); //default
print(foo("lalala".runtimeType)); //Hello Dart!
print(foo(foo.runtimeType)); //default
}
Is its sole purpose to be the return type for methods like runtimeType and type matching ?
I don't think you can use it for generics. There you need type literals. But you can use it for reflection.
Just one simple example:
import 'dart:mirrors' as mirr;
class A {
String s;
A(this.s);
#override
String toString() => s;
}
void main() {
Type type = A;
var str = "Hello Dart";
mirr.ClassMirror cm = mirr.reflectType(type);
var s = cm.newInstance(new Symbol(''), [str]).reflectee;
print(s);
}
You could also create a Map with registered factories for different types to avoid the need for reflection.
(not tested)
class A {
String s;
int a = 0;
int b = 0;
int c = 0;
A(this.s);
A.extended(this.s, this.a, this.b, this.c);
#override
String toString() => '${super.toString()}: $s, $a, $b, $c';
}
void main(args) {
Type t = A;
registerType(t, (List args) => new A.extended(args[0], args[1], args[2], args[3]));
...
var a = getInstance(t, ['hallo', 1, 2, 3]);
}
Map<Type,Function> _factories = {};
void registerType(Type t, Function factory) {
_factories[t] = factory;
}
void getNewInstance(Type t, List args) {
return _factories[t](args);
}

Resources