Skip to content

Boilerplate mixin for poor man's hashCode, operator== and toString methods #19181

Closed
@DartBot

Description

@DartBot

This issue was originally filed by [email protected]


Implementing boilerplate methods is cumbersome and error-prone, despite nice building blocks in quiver-dart and collection/equality.dart.

Relying on IDEs to generate them doesn't quite solve code bloat / maintenance issues, and relying on protobufs is a bit overkill / not idiomatic.

I propose to add a very simple Boilerplate mixin that implements these methods using collection/equality.dart on a list of public field values (see boilerplate.dart in attachment).

The list of field values can either be inferred using mirrors, or provided explicitly with a dedicated override (this override being shorter than the methods it helps implement).

This proposal addresses the general bean-like boilerplate but could be optimized for immutable types (Issue #10551, Issue #501) by caching the hashCode (either explicitly or by detecting the absence of setters).

Long story short, here's how Boilerplate could be used:

With mirrors:

    @­MirrorsUsed(targets: const[Foo, Bar], override: "*")
    import 'dart:mirrors';
    import 'boilerplate.dart';

    class Bar extends Boilerplate {
      final int i;
      Bar(this.i);
    }

    class Foo extends Bar {
      final int j;
      final String s;
      Foo(int i, this.j, this.s): super(i);
    }

    print(new Bar(1)); // "Bar { i: 1 }"
    print(new Foo(1, 2, "3")); // "Foo { i: 1, j: 2, s: 3 }"

    assert(new Bar(1) == new Bar(1));
    assert(new Bar(1) != new Bar(2));

Without mirrors:

    import 'boilerplate.dart';

    class Bar extends Boilerplate {
      final int i;
      Bar(this.i);
      @­override get fields => { "i": i };
      @­override get className => "Bar";
    }

    class Foo extends Bar {
      final int j;
      final String s;
      Foo(int i, this.j, this.s): super(i);
      @­override get fields => { "i": i, "j": j, "s": s };
      @­override get className => "Foo";
    }

Note that Boilerplate can be safely mixed in at any level:

     class A extends Boilerplate {}
     class B extends A with Boilerplate {}

I understand this doesn't solve all the potential use cases, but I think it's acceptable enough to make it to some pub package, for users who agree with the following explicit limitations:

  • Only uses public fields by default,
  • No special handling of reference cycles: user must avoid them responsibly,
  • Not optimized for speed,
  • Subsequent calls of hashCode on an object with mutable fields may yield different values (well, just as in Java).
  • Requires mirrors (with proper MirrorsUsed annotation) or explicit definition of fields and className.
    Attachment:
    boilerplate.dart (6.25 KB)

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-core-librarySDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries.closed-not-plannedClosed as we don't intend to take action on the reported issuetype-enhancementA request for a change that isn't a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions