0

I've got ClassA which holds a a List<ClassB>. ClassB has an string attribute.

If I now have one const Object of ClassA with a list of an object of ClassB completly identical to another non const Object of ClassA with the exact same object of ClassB then these two are not treated as equal.

Why? I could not find any documentation referencing this occurance when looking any documentation regarding equality.

Here's the code:

import 'package:test/test.dart';

void main() {
  test('equal', () {
    const ClassA a1 = ClassA(list: [ClassB(text: "Mo")]);
    ClassA a2 = ClassA(list: [ClassB(text: "Mo"),]);

    expect(const [ClassB(text: "Mo")], [ClassB(text: "Mo")]);//true
    expect(a1, equals(a2)); //false. Is only true when a2 is const.
  });
}

class ClassB {
  final String text;

  const ClassB({this.text});

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is ClassB &&
          runtimeType == other.runtimeType &&
          text == other.text;

  @override
  int get hashCode => text.hashCode;
}

class ClassA {
  final List<ClassB> list;
  const ClassA({this.list});

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is ClassA &&
          runtimeType == other.runtimeType &&
          list == other.list;

  @override
  int get hashCode => list.hashCode;
}

I expected a1 and a2 as being equal.

JSAN L.
  • 442
  • 1
  • 3
  • 15

1 Answers1

1

The problem is that list and other.list are only equal if they are both const (and with the same, const values, of course), as they are then the same object.

package:collections has some useful comparison tools.

Your equals operator can be rewritten as:

import 'package:collection/collection.dart';
...  
  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is ClassA && ListEquality<ClassB>().equals(list, other.list);

You will also need to change your implementation of hashCode as, with the change above, the classes are now equal but have differing hashCodes. See edit below...

See also.

Edit

class ClassA {
  final List<ClassB> list;
  final ListEquality<ClassB> equality = const ListEquality<ClassB>();

  const ClassA({this.list});

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
      other is ClassA && equality.equals(list, other.list);
  }

  @override
  int get hashCode => equality.hash(list);
}
Richard Heap
  • 48,344
  • 9
  • 130
  • 112
  • Thats what I guessed, but why is `expect(const [ClassB(text: "Mo")], [ClassB(text: "Mo")]);` `true` then? One is const, the other is not. – JSAN L. May 08 '19 at 17:55
  • Good question, because `print(const [ClassB(text: "Mo")] == [ClassB(text: "Mo")]);` prints `false`! – Richard Heap May 08 '19 at 17:57
  • Yeah, I now know why it works with equals(), but not with ==. Documentation of equals() Matcher: *For [Iterable]s and [Map]s, this will recursively match the elements. To handle cyclic structures a recursion depth [limit] can be provided. The default limit is 100. [Set]s will be compared order-independently.* This is also the reason why ClassA would not work with equals(), as it isn't a List/Map. That clarified it, thank you! – JSAN L. May 08 '19 at 18:11
  • How would I need to rewrite the `hashCode` with your implementation? – JSAN L. May 08 '19 at 18:18
  • 1
    The reason it works with `expect` is that `expect` recognizes lists specially. The Dart `List` implementations don't override `operator==`, so two list objects are only equal if they are the same object. The `expect` function considers two lists equal if they have equal elements, not just if they are equal as lists. – lrn May 09 '19 at 14:20