General clean-up and tweaks. (#157)

* General clean-up and tweaks.

Fixes some null-safety issues, usees some newer features (const classes can use mixins now),
and replaces unnecessary `late` with a dual constructor.

Fixes the `CombinedList.iterator` having potentially quadratic complexity
since the default list iterator does repeated indexing.

Ensure that interfaces returning an `Iterable` does not return a `Set` instead.
diff --git a/lib/src/algorithms.dart b/lib/src/algorithms.dart
index fc7aed3..44a2b3b 100644
--- a/lib/src/algorithms.dart
+++ b/lib/src/algorithms.dart
@@ -199,7 +199,7 @@
     var max = targetOffset + i;
     while (min < max) {
       var mid = min + ((max - min) >> 1);
-      if (compare(element, target[mid]!) < 0) {
+      if (compare(element, target[mid]) < 0) {
         max = mid;
       } else {
         min = mid + 1;
@@ -262,7 +262,7 @@
   var cursor1 = firstStart;
   var cursor2 = secondStart;
   var firstElement = firstList[cursor1++];
-  var secondElement = secondList[cursor2++]!;
+  var secondElement = secondList[cursor2++];
   while (true) {
     if (compare(firstElement, secondElement) <= 0) {
       target[targetOffset++] = firstElement;
@@ -271,7 +271,7 @@
     } else {
       target[targetOffset++] = secondElement;
       if (cursor2 != secondEnd) {
-        secondElement = secondList[cursor2++]!;
+        secondElement = secondList[cursor2++];
         continue;
       }
       // Second list empties first. Flushing first list here.
diff --git a/lib/src/canonicalized_map.dart b/lib/src/canonicalized_map.dart
index 280050c..5f97b25 100644
--- a/lib/src/canonicalized_map.dart
+++ b/lib/src/canonicalized_map.dart
@@ -4,8 +4,6 @@
 
 import 'dart:collection';
 
-import 'utils.dart';
-
 /// A map whose keys are converted to canonical values of type `C`.
 ///
 /// This is useful for using case-insensitive String keys, for example. It's
@@ -17,7 +15,7 @@
 
   final bool Function(K)? _isValidKeyFn;
 
-  final _base = <C, Pair<K, V>>{};
+  final _base = <C, MapEntry<K, V>>{};
 
   /// Creates an empty canonicalized map.
   ///
@@ -52,13 +50,13 @@
   V? operator [](Object? key) {
     if (!_isValidKey(key)) return null;
     var pair = _base[_canonicalize(key as K)];
-    return pair == null ? null : pair.last;
+    return pair == null ? null : pair.value;
   }
 
   @override
   void operator []=(K key, V value) {
     if (!_isValidKey(key)) return;
-    _base[_canonicalize(key)] = Pair(key, value);
+    _base[_canonicalize(key)] = MapEntry(key, value);
   }
 
   @override
@@ -67,8 +65,8 @@
   }
 
   @override
-  void addEntries(Iterable<MapEntry<K, V>> entries) => _base.addEntries(
-      entries.map((e) => MapEntry(_canonicalize(e.key), Pair(e.key, e.value))));
+  void addEntries(Iterable<MapEntry<K, V>> entries) => _base.addEntries(entries
+      .map((e) => MapEntry(_canonicalize(e.key), MapEntry(e.key, e.value))));
 
   @override
   Map<K2, V2> cast<K2, V2>() => _base.cast<K2, V2>();
@@ -86,15 +84,15 @@
 
   @override
   bool containsValue(Object? value) =>
-      _base.values.any((pair) => pair.last == value);
+      _base.values.any((pair) => pair.value == value);
 
   @override
   Iterable<MapEntry<K, V>> get entries =>
-      _base.entries.map((e) => MapEntry(e.value.first, e.value.last));
+      _base.entries.map((e) => MapEntry(e.value.key, e.value.value));
 
   @override
   void forEach(void Function(K, V) f) {
-    _base.forEach((key, pair) => f(pair.first, pair.last));
+    _base.forEach((key, pair) => f(pair.key, pair.value));
   }
 
   @override
@@ -104,83 +102,63 @@
   bool get isNotEmpty => _base.isNotEmpty;
 
   @override
-  Iterable<K> get keys => _base.values.map((pair) => pair.first);
+  Iterable<K> get keys => _base.values.map((pair) => pair.key);
 
   @override
   int get length => _base.length;
 
   @override
   Map<K2, V2> map<K2, V2>(MapEntry<K2, V2> Function(K, V) transform) =>
-      _base.map((_, pair) => transform(pair.first, pair.last));
+      _base.map((_, pair) => transform(pair.key, pair.value));
 
   @override
   V putIfAbsent(K key, V Function() ifAbsent) {
     return _base
-        .putIfAbsent(_canonicalize(key), () => Pair(key, ifAbsent()))
-        .last;
+        .putIfAbsent(_canonicalize(key), () => MapEntry(key, ifAbsent()))
+        .value;
   }
 
   @override
   V? remove(Object? key) {
     if (!_isValidKey(key)) return null;
     var pair = _base.remove(_canonicalize(key as K));
-    return pair == null ? null : pair.last;
+    return pair?.value;
   }
 
   @override
   void removeWhere(bool Function(K key, V value) test) =>
-      _base.removeWhere((_, pair) => test(pair.first, pair.last));
+      _base.removeWhere((_, pair) => test(pair.key, pair.value));
 
   @deprecated
   Map<K2, V2> retype<K2, V2>() => cast<K2, V2>();
 
   @override
-  V update(K key, V Function(V) update, {V Function()? ifAbsent}) => _base
-      .update(_canonicalize(key), (pair) => Pair(key, update(pair.last)),
-          ifAbsent: ifAbsent == null ? null : () => Pair(key, ifAbsent()))
-      .last;
+  V update(K key, V Function(V) update, {V Function()? ifAbsent}) =>
+      _base.update(_canonicalize(key), (pair) {
+        var value = pair.value;
+        var newValue = update(value);
+        if (identical(newValue, value)) return pair;
+        return MapEntry(key, newValue);
+      },
+          ifAbsent:
+              ifAbsent == null ? null : () => MapEntry(key, ifAbsent())).value;
 
   @override
-  void updateAll(V Function(K key, V value) update) => _base
-      .updateAll((_, pair) => Pair(pair.first, update(pair.first, pair.last)));
-
-  @override
-  Iterable<V> get values => _base.values.map((pair) => pair.last);
-
-  @override
-  String toString() {
-    // Detect toString() cycles.
-    if (_isToStringVisiting(this)) {
-      return '{...}';
-    }
-
-    var result = StringBuffer();
-    try {
-      _toStringVisiting.add(this);
-      result.write('{');
-      var first = true;
-      forEach((k, v) {
-        if (!first) {
-          result.write(', ');
-        }
-        first = false;
-        result.write('$k: $v');
+  void updateAll(V Function(K key, V value) update) =>
+      _base.updateAll((_, pair) {
+        var value = pair.value;
+        var key = pair.key;
+        var newValue = update(key, value);
+        if (identical(value, newValue)) return pair;
+        return MapEntry(key, newValue);
       });
-      result.write('}');
-    } finally {
-      assert(identical(_toStringVisiting.last, this));
-      _toStringVisiting.removeLast();
-    }
 
-    return result.toString();
-  }
+  @override
+  Iterable<V> get values => _base.values.map((pair) => pair.value);
+
+  @override
+  String toString() => MapBase.mapToString(this);
 
   bool _isValidKey(Object? key) =>
       (key is K) && (_isValidKeyFn == null || _isValidKeyFn!(key));
 }
-
-/// A collection used to identify cyclic maps during toString() calls.
-final List _toStringVisiting = [];
-
-/// Check if we are currently visiting `o` in a toString() call.
-bool _isToStringVisiting(o) => _toStringVisiting.any((e) => identical(o, e));
diff --git a/lib/src/combined_wrappers/combined_iterable.dart b/lib/src/combined_wrappers/combined_iterable.dart
index 8c5f091..97d843f 100644
--- a/lib/src/combined_wrappers/combined_iterable.dart
+++ b/lib/src/combined_wrappers/combined_iterable.dart
@@ -4,6 +4,8 @@
 
 import 'dart:collection';
 
+import 'combined_iterator.dart';
+
 /// A view of several iterables combined sequentially into a single iterable.
 ///
 /// All methods and accessors treat the [CombinedIterableView] as if it were a
@@ -20,7 +22,7 @@
 
   @override
   Iterator<T> get iterator =>
-      _CombinedIterator<T>(_iterables.map((i) => i.iterator).iterator);
+      CombinedIterator<T>(_iterables.map((i) => i.iterator).iterator);
 
   // Special cased contains/isEmpty/length since many iterables have an
   // efficient implementation instead of running through the entire iterator.
@@ -34,41 +36,3 @@
   @override
   int get length => _iterables.fold(0, (length, i) => length + i.length);
 }
-
-/// The iterator for [CombinedIterableView].
-///
-/// This moves through each iterable's iterators in sequence.
-class _CombinedIterator<T> implements Iterator<T> {
-  /// The iterators that this combines.
-  ///
-  /// Because this comes from a call to [Iterable.map], it's lazy and will
-  /// avoid instantiating unnecessary iterators.
-  final Iterator<Iterator<T>> _iterators;
-
-  /// The current iterator in [_iterators], or `null` if done iterating.
-  Iterator<T>? _currentItr;
-
-  _CombinedIterator(this._iterators) {
-    _advance();
-  }
-
-  @override
-  T get current => _iterators.current.current;
-
-  @override
-  bool moveNext() {
-    if (_currentItr == null) return false;
-    if (_currentItr!.moveNext()) {
-      return true;
-    } else {
-      _advance();
-    }
-    return moveNext();
-  }
-
-  /// Advances [_currentItr] or sets it to `null` if there are no more entries
-  /// in [_iterators].
-  void _advance() {
-    _currentItr = _iterators.moveNext() ? _iterators.current : null;
-  }
-}
diff --git a/lib/src/combined_wrappers/combined_iterator.dart b/lib/src/combined_wrappers/combined_iterator.dart
new file mode 100644
index 0000000..0d6088a
--- /dev/null
+++ b/lib/src/combined_wrappers/combined_iterator.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2020, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// The iterator for `CombinedIterableView` and `CombinedListView`.
+///
+/// Moves through each iterable's iterator in sequence.
+class CombinedIterator<T> implements Iterator<T> {
+  /// The iterators that this combines, or `null` if done iterating.
+  ///
+  /// Because this comes from a call to [Iterable.map], it's lazy and will
+  /// avoid instantiating unnecessary iterators.
+  Iterator<Iterator<T>>? _iterators;
+
+  CombinedIterator(Iterator<Iterator<T>> iterators) : _iterators = iterators {
+    if (!iterators.moveNext()) _iterators = null;
+  }
+
+  @override
+  T get current {
+    var iterators = _iterators;
+    if (iterators != null) return iterators.current.current;
+    return null as T;
+  }
+
+  @override
+  bool moveNext() {
+    var iterators = _iterators;
+    if (iterators != null) {
+      do {
+        if (iterators.current.moveNext()) {
+          return true;
+        }
+      } while (iterators.moveNext());
+      _iterators = null;
+    }
+    return false;
+  }
+}
diff --git a/lib/src/combined_wrappers/combined_list.dart b/lib/src/combined_wrappers/combined_list.dart
index e341be4..c9a2eb8 100644
--- a/lib/src/combined_wrappers/combined_list.dart
+++ b/lib/src/combined_wrappers/combined_list.dart
@@ -4,6 +4,8 @@
 
 import 'dart:collection';
 
+import 'combined_iterator.dart';
+
 /// A view of several lists combined into a single list.
 ///
 /// All methods and accessors treat the [CombinedListView] list as if it were a
@@ -26,6 +28,10 @@
   CombinedListView(this._lists);
 
   @override
+  Iterator<T> get iterator =>
+      CombinedIterator<T>(_lists.map((i) => i.iterator).iterator);
+
+  @override
   set length(int length) {
     _throw();
   }
diff --git a/lib/src/empty_unmodifiable_set.dart b/lib/src/empty_unmodifiable_set.dart
index 6d7f03c..44bd5ef 100644
--- a/lib/src/empty_unmodifiable_set.dart
+++ b/lib/src/empty_unmodifiable_set.dart
@@ -4,24 +4,20 @@
 
 import 'dart:collection';
 
+import 'package:collection/collection.dart';
+
 import 'unmodifiable_wrappers.dart';
 
-// Unfortunately, we can't use UnmodifiableSetMixin here, since const classes
-// can't use mixins.
-/// An unmodifiable, empty set that can have a const constructor.
+/// An unmodifiable, empty set which can be constant.
 class EmptyUnmodifiableSet<E> extends IterableBase<E>
+    with UnmodifiableSetMixin<E>
     implements UnmodifiableSetView<E> {
-  static T _throw<T>() {
-    throw UnsupportedError('Cannot modify an unmodifiable Set');
-  }
+  const EmptyUnmodifiableSet();
 
   @override
   Iterator<E> get iterator => Iterable<E>.empty().iterator;
   @override
   int get length => 0;
-
-  const EmptyUnmodifiableSet();
-
   @override
   EmptyUnmodifiableSet<T> cast<T>() => EmptyUnmodifiableSet<T>();
   @override
@@ -29,7 +25,7 @@
   @override
   bool containsAll(Iterable<Object?> other) => other.isEmpty;
   @override
-  Iterable<E> followedBy(Iterable<E> other) => Set.from(other);
+  Iterable<E> followedBy(Iterable<E> other) => DelegatingIterable(other);
   @override
   E? lookup(Object? element) => null;
   @deprecated
@@ -37,32 +33,15 @@
   EmptyUnmodifiableSet<T> retype<T>() => EmptyUnmodifiableSet<T>();
   @override
   E singleWhere(bool Function(E) test, {E Function()? orElse}) =>
-      super.singleWhere(test);
+      orElse != null ? orElse() : throw StateError('No element');
   @override
-  Iterable<T> whereType<T>() => EmptyUnmodifiableSet<T>();
+  Iterable<T> whereType<T>() => Iterable.empty();
   @override
   Set<E> toSet() => {};
   @override
-  Set<E> union(Set<E> other) => Set.from(other);
+  Set<E> union(Set<E> other) => Set.of(other);
   @override
   Set<E> intersection(Set<Object?> other) => {};
   @override
   Set<E> difference(Set<Object?> other) => {};
-
-  @override
-  bool add(E value) => _throw();
-  @override
-  void addAll(Iterable<E> elements) => _throw();
-  @override
-  void clear() => _throw();
-  @override
-  bool remove(Object? element) => _throw();
-  @override
-  void removeAll(Iterable<Object?> elements) => _throw();
-  @override
-  void removeWhere(bool Function(E) test) => _throw();
-  @override
-  void retainWhere(bool Function(E) test) => _throw();
-  @override
-  void retainAll(Iterable<Object?> elements) => _throw();
 }
diff --git a/lib/src/functions.dart b/lib/src/functions.dart
index 8f401d4..07c8d4b 100644
--- a/lib/src/functions.dart
+++ b/lib/src/functions.dart
@@ -31,7 +31,7 @@
 /// values. If [value] is omitted, the value from [map2] is used.
 Map<K, V> mergeMaps<K, V>(Map<K, V> map1, Map<K, V> map2,
     {V Function(V, V)? value}) {
-  var result = Map<K, V>.from(map1);
+  var result = Map<K, V>.of(map1);
   if (value == null) return result..addAll(map2);
 
   map2.forEach((key, mapValue) {
diff --git a/lib/src/iterable_zip.dart b/lib/src/iterable_zip.dart
index 940b5e7..9671eaf 100644
--- a/lib/src/iterable_zip.dart
+++ b/lib/src/iterable_zip.dart
@@ -23,7 +23,6 @@
   @override
   Iterator<List<T>> get iterator {
     var iterators = _iterables.map((x) => x.iterator).toList(growable: false);
-    // TODO(lrn): Return an empty iterator directly if iterators is empty?
     return _IteratorZip<T>(iterators);
   }
 }
@@ -49,5 +48,5 @@
   }
 
   @override
-  List<T> get current => _current!;
+  List<T> get current => _current ?? (throw StateError('No element'));
 }
diff --git a/lib/src/queue_list.dart b/lib/src/queue_list.dart
index 14b696e..3bf13f2 100644
--- a/lib/src/queue_list.dart
+++ b/lib/src/queue_list.dart
@@ -25,26 +25,25 @@
     return _CastQueueList<S, T>(source);
   }
 
-  static const int _INITIAL_CAPACITY = 8;
-  late List<E?> _table;
+  /// Default and minimal initial capacity of the queue-list.
+  static const int _initialCapacity = 8;
+  List<E?> _table;
   int _head;
   int _tail;
 
-  /// Create an empty queue.
+  /// Creates an empty queue.
   ///
   /// If [initialCapacity] is given, prepare the queue for at least that many
   /// elements.
   QueueList([int? initialCapacity])
-      : _head = 0,
-        _tail = 0 {
-    if (initialCapacity == null || initialCapacity < _INITIAL_CAPACITY) {
-      initialCapacity = _INITIAL_CAPACITY;
-    } else if (!_isPowerOf2(initialCapacity)) {
-      initialCapacity = _nextPowerOf2(initialCapacity);
-    }
-    assert(_isPowerOf2(initialCapacity));
-    _table = List<E?>.filled(initialCapacity, null);
-  }
+      : this._init(_computeInitialCapacity(initialCapacity));
+
+  /// Creates an empty queue with the specific initial capacity.
+  QueueList._init(int initialCapacity)
+      : assert(_isPowerOf2(initialCapacity)),
+        _table = List<E?>.filled(initialCapacity, null),
+        _head = 0,
+        _tail = 0;
 
   /// An internal constructor for use by [_CastQueueList].
   QueueList._(this._head, this._tail, this._table);
@@ -64,6 +63,18 @@
     }
   }
 
+  /// Computes the actual initial capacity based on the constructor parameter.
+  static int _computeInitialCapacity(int? initialCapacity) {
+    if (initialCapacity == null || initialCapacity < _initialCapacity) {
+      return _initialCapacity;
+    }
+    initialCapacity += 1;
+    if (_isPowerOf2(initialCapacity)) {
+      return initialCapacity;
+    }
+    return _nextPowerOf2(initialCapacity);
+  }
+
   // Collection interface.
 
   @override
@@ -137,7 +148,7 @@
   E removeLast() {
     if (_head == _tail) throw StateError('No element');
     _tail = (_tail - 1) & (_table.length - 1);
-    var result = _table[_tail]!;
+    var result = _table[_tail] as E;
     _table[_tail] = null;
     return result;
   }
diff --git a/lib/src/union_set.dart b/lib/src/union_set.dart
index 2fe6e3e..37aee48 100644
--- a/lib/src/union_set.dart
+++ b/lib/src/union_set.dart
@@ -29,7 +29,9 @@
   /// is, that they contain no elements in common. This makes many operations
   /// including [length] more efficient. If the component sets turn out not to
   /// be disjoint, some operations may behave inconsistently.
-  UnionSet(this._sets, {bool disjoint = false}) : _disjoint = disjoint;
+  UnionSet(Set<Set<E>> sets, {bool disjoint = false})
+      : _sets = sets,
+        _disjoint = disjoint;
 
   /// Creates a new set that's a view of the union of all sets in [sets].
   ///
@@ -66,20 +68,13 @@
 
   @override
   E? lookup(Object? element) {
-    if (element == null) return null;
     for (var set in _sets) {
       var result = set.lookup(element);
-      if (result != null) return result;
+      if (result != null || set.contains(null)) return result;
     }
     return null;
   }
 
   @override
-  Set<E> toSet() {
-    var result = <E>{};
-    for (var set in _sets) {
-      result.addAll(set);
-    }
-    return result;
-  }
+  Set<E> toSet() => <E>{for (var set in _sets) ...set};
 }
diff --git a/lib/src/union_set_controller.dart b/lib/src/union_set_controller.dart
index 245081d..ea3622a 100644
--- a/lib/src/union_set_controller.dart
+++ b/lib/src/union_set_controller.dart
@@ -23,20 +23,21 @@
 /// ```
 class UnionSetController<E> {
   /// The [UnionSet] that provides a view of the union of sets in [this].
-  UnionSet<E> get set => _set;
-  late final UnionSet<E> _set;
+  final UnionSet<E> set;
 
   /// The sets whose union is exposed through [set].
-  final _sets = <Set<E>>{};
+  final Set<Set<E>> _sets;
 
   /// Creates a set of sets that provides a view of the union of those sets.
   ///
   /// If [disjoint] is `true`, this assumes that all component sets are
   /// disjoint—that is, that they contain no elements in common. This makes
   /// many operations including [length] more efficient.
-  UnionSetController({bool disjoint = false}) {
-    _set = UnionSet<E>(_sets, disjoint: disjoint);
-  }
+  UnionSetController({bool disjoint = false}) : this._(<Set<E>>{}, disjoint);
+
+  /// Creates a controller with the provided [_sets].
+  UnionSetController._(this._sets, bool disjoint)
+      : set = UnionSet<E>(_sets, disjoint: disjoint);
 
   /// Adds the contents of [component] to [set].
   ///
diff --git a/lib/src/unmodifiable_wrappers.dart b/lib/src/unmodifiable_wrappers.dart
index 96551e8..c6cf2cd 100644
--- a/lib/src/unmodifiable_wrappers.dart
+++ b/lib/src/unmodifiable_wrappers.dart
@@ -26,7 +26,7 @@
 /// Mixin class that implements a throwing version of all list operations that
 /// change the List's length.
 abstract class NonGrowableListMixin<E> implements List<E> {
-  static T _throw<T>() {
+  static Never _throw() {
     throw UnsupportedError('Cannot change the length of a fixed-length list');
   }
 
@@ -98,9 +98,9 @@
 
 /// An unmodifiable set.
 ///
-/// An UnmodifiableSetView contains a [Set] object and ensures
-/// that it does not change.
-/// Methods that would change the set,
+/// An [UnmodifiableSetView] contains a [Set],
+/// and prevents that set from being changed through the view.
+/// Methods that could change the set,
 /// such as [add] and [remove], throw an [UnsupportedError].
 /// Permitted operations defer to the wrapped set.
 class UnmodifiableSetView<E> extends DelegatingSet<E>
@@ -117,7 +117,7 @@
 /// Mixin class that implements a throwing version of all set operations that
 /// change the Set.
 abstract class UnmodifiableSetMixin<E> implements Set<E> {
-  static T _throw<T>() {
+  static Never _throw() {
     throw UnsupportedError('Cannot modify an unmodifiable Set');
   }
 
@@ -165,7 +165,7 @@
 /// Mixin class that implements a throwing version of all map operations that
 /// change the Map.
 abstract class UnmodifiableMapMixin<K, V> implements Map<K, V> {
-  static T _throw<T>() {
+  static Never _throw() {
     throw UnsupportedError('Cannot modify an unmodifiable Map');
   }
 
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 194f99c..00ea932 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -2,14 +2,6 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-/// A pair of values.
-class Pair<E, F> {
-  E first;
-  F last;
-
-  Pair(this.first, this.last);
-}
-
 /// Returns a [Comparator] that asserts that its first argument is comparable.
 Comparator<T> defaultCompare<T>() =>
     (value1, value2) => (value1 as Comparable).compareTo(value2);
diff --git a/lib/src/wrappers.dart b/lib/src/wrappers.dart
index 14e2d8c..b5b61e3 100644
--- a/lib/src/wrappers.dart
+++ b/lib/src/wrappers.dart
@@ -148,8 +148,11 @@
 /// This class can be used to hide non-`List` methods of a list object, or it
 /// can be extended to add extra functionality on top of an existing list
 /// object.
-class DelegatingList<E> extends DelegatingIterable<E> implements List<E> {
-  const DelegatingList(List<E> base) : super(base);
+class DelegatingList<E> extends _DelegatingIterableBase<E> implements List<E> {
+  @override
+  final List<E> _base;
+
+  const DelegatingList(List<E> base) : _base = base;
 
   /// Creates a wrapper that asserts the types of values in [base].
   ///
@@ -165,43 +168,41 @@
   @Deprecated('Use list.cast<E> instead.')
   static List<E> typed<E>(List base) => base.cast<E>();
 
-  List<E> get _listBase => _base as List<E>;
-
   @override
-  E operator [](int index) => _listBase[index];
+  E operator [](int index) => _base[index];
 
   @override
   void operator []=(int index, E value) {
-    _listBase[index] = value;
+    _base[index] = value;
   }
 
   @override
-  List<E> operator +(List<E> other) => _listBase + other;
+  List<E> operator +(List<E> other) => _base + other;
 
   @override
   void add(E value) {
-    _listBase.add(value);
+    _base.add(value);
   }
 
   @override
   void addAll(Iterable<E> iterable) {
-    _listBase.addAll(iterable);
+    _base.addAll(iterable);
   }
 
   @override
-  Map<int, E> asMap() => _listBase.asMap();
+  Map<int, E> asMap() => _base.asMap();
 
   @override
-  List<T> cast<T>() => _listBase.cast<T>();
+  List<T> cast<T>() => _base.cast<T>();
 
   @override
   void clear() {
-    _listBase.clear();
+    _base.clear();
   }
 
   @override
   void fillRange(int start, int end, [E? fillValue]) {
-    _listBase.fillRange(start, end, fillValue);
+    _base.fillRange(start, end, fillValue);
   }
 
   @override
@@ -211,23 +212,23 @@
   }
 
   @override
-  Iterable<E> getRange(int start, int end) => _listBase.getRange(start, end);
+  Iterable<E> getRange(int start, int end) => _base.getRange(start, end);
 
   @override
-  int indexOf(E element, [int start = 0]) => _listBase.indexOf(element, start);
+  int indexOf(E element, [int start = 0]) => _base.indexOf(element, start);
 
   @override
   int indexWhere(bool Function(E) test, [int start = 0]) =>
-      _listBase.indexWhere(test, start);
+      _base.indexWhere(test, start);
 
   @override
   void insert(int index, E element) {
-    _listBase.insert(index, element);
+    _base.insert(index, element);
   }
 
   @override
   void insertAll(int index, Iterable<E> iterable) {
-    _listBase.insertAll(index, iterable);
+    _base.insertAll(index, iterable);
   }
 
   @override
@@ -237,45 +238,44 @@
   }
 
   @override
-  int lastIndexOf(E element, [int? start]) =>
-      _listBase.lastIndexOf(element, start);
+  int lastIndexOf(E element, [int? start]) => _base.lastIndexOf(element, start);
 
   @override
   int lastIndexWhere(bool Function(E) test, [int? start]) =>
-      _listBase.lastIndexWhere(test, start);
+      _base.lastIndexWhere(test, start);
 
   @override
   set length(int newLength) {
-    _listBase.length = newLength;
+    _base.length = newLength;
   }
 
   @override
-  bool remove(Object? value) => _listBase.remove(value);
+  bool remove(Object? value) => _base.remove(value);
 
   @override
-  E removeAt(int index) => _listBase.removeAt(index);
+  E removeAt(int index) => _base.removeAt(index);
 
   @override
-  E removeLast() => _listBase.removeLast();
+  E removeLast() => _base.removeLast();
 
   @override
   void removeRange(int start, int end) {
-    _listBase.removeRange(start, end);
+    _base.removeRange(start, end);
   }
 
   @override
   void removeWhere(bool Function(E) test) {
-    _listBase.removeWhere(test);
+    _base.removeWhere(test);
   }
 
   @override
   void replaceRange(int start, int end, Iterable<E> iterable) {
-    _listBase.replaceRange(start, end, iterable);
+    _base.replaceRange(start, end, iterable);
   }
 
   @override
   void retainWhere(bool Function(E) test) {
-    _listBase.retainWhere(test);
+    _base.retainWhere(test);
   }
 
   @deprecated
@@ -283,38 +283,41 @@
   List<T> retype<T>() => cast<T>();
 
   @override
-  Iterable<E> get reversed => _listBase.reversed;
+  Iterable<E> get reversed => _base.reversed;
 
   @override
   void setAll(int index, Iterable<E> iterable) {
-    _listBase.setAll(index, iterable);
+    _base.setAll(index, iterable);
   }
 
   @override
   void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
-    _listBase.setRange(start, end, iterable, skipCount);
+    _base.setRange(start, end, iterable, skipCount);
   }
 
   @override
   void shuffle([math.Random? random]) {
-    _listBase.shuffle(random);
+    _base.shuffle(random);
   }
 
   @override
   void sort([int Function(E, E)? compare]) {
-    _listBase.sort(compare);
+    _base.sort(compare);
   }
 
   @override
-  List<E> sublist(int start, [int? end]) => _listBase.sublist(start, end);
+  List<E> sublist(int start, [int? end]) => _base.sublist(start, end);
 }
 
 /// A [Set] that delegates all operations to a base set.
 ///
 /// This class can be used to hide non-`Set` methods of a set object, or it can
 /// be extended to add extra functionality on top of an existing set object.
-class DelegatingSet<E> extends DelegatingIterable<E> implements Set<E> {
-  const DelegatingSet(Set<E> base) : super(base);
+class DelegatingSet<E> extends _DelegatingIterableBase<E> implements Set<E> {
+  @override
+  final Set<E> _base;
+
+  const DelegatingSet(Set<E> base) : _base = base;
 
   /// Creates a wrapper that asserts the types of values in [base].
   ///
@@ -330,52 +333,50 @@
   @Deprecated('Use set.cast<E> instead.')
   static Set<E> typed<E>(Set base) => base.cast<E>();
 
-  Set<E> get _setBase => _base as Set<E>;
-
   @override
-  bool add(E value) => _setBase.add(value);
+  bool add(E value) => _base.add(value);
 
   @override
   void addAll(Iterable<E> elements) {
-    _setBase.addAll(elements);
+    _base.addAll(elements);
   }
 
   @override
-  Set<T> cast<T>() => _setBase.cast<T>();
+  Set<T> cast<T>() => _base.cast<T>();
 
   @override
   void clear() {
-    _setBase.clear();
+    _base.clear();
   }
 
   @override
-  bool containsAll(Iterable<Object?> other) => _setBase.containsAll(other);
+  bool containsAll(Iterable<Object?> other) => _base.containsAll(other);
 
   @override
-  Set<E> difference(Set<Object?> other) => _setBase.difference(other);
+  Set<E> difference(Set<Object?> other) => _base.difference(other);
 
   @override
-  Set<E> intersection(Set<Object?> other) => _setBase.intersection(other);
+  Set<E> intersection(Set<Object?> other) => _base.intersection(other);
 
   @override
-  E? lookup(Object? element) => _setBase.lookup(element);
+  E? lookup(Object? element) => _base.lookup(element);
 
   @override
-  bool remove(Object? value) => _setBase.remove(value);
+  bool remove(Object? value) => _base.remove(value);
 
   @override
   void removeAll(Iterable<Object?> elements) {
-    _setBase.removeAll(elements);
+    _base.removeAll(elements);
   }
 
   @override
   void removeWhere(bool Function(E) test) {
-    _setBase.removeWhere(test);
+    _base.removeWhere(test);
   }
 
   @override
   void retainAll(Iterable<Object?> elements) {
-    _setBase.retainAll(elements);
+    _base.retainAll(elements);
   }
 
   @deprecated
@@ -384,14 +385,14 @@
 
   @override
   void retainWhere(bool Function(E) test) {
-    _setBase.retainWhere(test);
+    _base.retainWhere(test);
   }
 
   @override
-  Set<E> union(Set<E> other) => _setBase.union(other);
+  Set<E> union(Set<E> other) => _base.union(other);
 
   @override
-  Set<E> toSet() => DelegatingSet<E>(_setBase.toSet());
+  Set<E> toSet() => DelegatingSet<E>(_base.toSet());
 }
 
 /// A [Queue] that delegates all operations to a base queue.
@@ -399,8 +400,12 @@
 /// This class can be used to hide non-`Queue` methods of a queue object, or it
 /// can be extended to add extra functionality on top of an existing queue
 /// object.
-class DelegatingQueue<E> extends DelegatingIterable<E> implements Queue<E> {
-  const DelegatingQueue(Queue<E> queue) : super(queue);
+class DelegatingQueue<E> extends _DelegatingIterableBase<E>
+    implements Queue<E> {
+  @override
+  final Queue<E> _base;
+
+  const DelegatingQueue(Queue<E> queue) : _base = queue;
 
   /// Creates a wrapper that asserts the types of values in [base].
   ///
@@ -416,47 +421,45 @@
   @Deprecated('Use queue.cast<E> instead.')
   static Queue<E> typed<E>(Queue base) => base.cast<E>();
 
-  Queue<E> get _baseQueue => _base as Queue<E>;
-
   @override
   void add(E value) {
-    _baseQueue.add(value);
+    _base.add(value);
   }
 
   @override
   void addAll(Iterable<E> iterable) {
-    _baseQueue.addAll(iterable);
+    _base.addAll(iterable);
   }
 
   @override
   void addFirst(E value) {
-    _baseQueue.addFirst(value);
+    _base.addFirst(value);
   }
 
   @override
   void addLast(E value) {
-    _baseQueue.addLast(value);
+    _base.addLast(value);
   }
 
   @override
-  Queue<T> cast<T>() => _baseQueue.cast<T>();
+  Queue<T> cast<T>() => _base.cast<T>();
 
   @override
   void clear() {
-    _baseQueue.clear();
+    _base.clear();
   }
 
   @override
-  bool remove(Object? object) => _baseQueue.remove(object);
+  bool remove(Object? object) => _base.remove(object);
 
   @override
   void removeWhere(bool Function(E) test) {
-    _baseQueue.removeWhere(test);
+    _base.removeWhere(test);
   }
 
   @override
   void retainWhere(bool Function(E) test) {
-    _baseQueue.retainWhere(test);
+    _base.retainWhere(test);
   }
 
   @deprecated
@@ -464,10 +467,10 @@
   Queue<T> retype<T>() => cast<T>();
 
   @override
-  E removeFirst() => _baseQueue.removeFirst();
+  E removeFirst() => _base.removeFirst();
 
   @override
-  E removeLast() => _baseQueue.removeLast();
+  E removeLast() => _base.removeLast();
 }
 
 /// A [Map] that delegates all operations to a base map.
@@ -616,7 +619,7 @@
   int get length => _baseMap.length;
 
   @override
-  String toString() => "{${_base.join(', ')}}";
+  String toString() => SetBase.setToString(this);
 
   @override
   bool containsAll(Iterable<Object?> other) => other.every(contains);
@@ -805,7 +808,7 @@
       var key = _keyForValue(element);
 
       if (!_baseMap.containsKey(key)) continue;
-      valuesToRetain.add(_baseMap[key]!);
+      valuesToRetain.add(_baseMap[key] ?? null as V);
     }
 
     var keysToRemove = [];