Dart UnmodifiableSetMixin
last modified April 4, 2025
In Dart, UnmodifiableSetMixin is a mixin that provides an unmodifiable Set interface. It prevents modifications to the underlying Set after creation.
The mixin implements all Set operations that don't modify the Set. Any attempt to modify the Set will throw an UnsupportedError. It's useful for creating immutable Set views.
Basic UnmodifiableSetMixin Usage
Here's how to create a simple unmodifiable Set using the mixin.
import 'dart:collection'; class UnmodifiableSet<E> extends SetBase<E> with UnmodifiableSetMixin<E> { final Set<E> _set; UnmodifiableSet(this._set); @override Iterator<E> get iterator => _set.iterator; @override int get length => _set.length; @override bool contains(Object? element) => _set.contains(element); } void main() { var numbers = {1, 2, 3}; var unmodifiable = UnmodifiableSet(numbers); print(unmodifiable); // {1, 2, 3} try { unmodifiable.add(4); // Throws UnsupportedError } catch (e) { print('Error: $e'); } }
We create a custom UnmodifiableSet class that uses UnmodifiableSetMixin. The mixin provides read-only operations while preventing modifications. Attempts to modify throw UnsupportedError.
$ dart main.dart {1, 2, 3} Error: Unsupported operation: Cannot modify an unmodifiable set
Creating UnmodifiableSetView
Dart provides UnmodifiableSetView which internally uses UnmodifiableSetMixin.
import 'dart:collection'; void main() { var fruits = {'apple', 'banana', 'orange'}; var unmodifiableFruits = UnmodifiableSetView(fruits); print('Original: $fruits'); print('Unmodifiable view: $unmodifiableFruits'); // Read operations work print('Contains apple? ${unmodifiableFruits.contains('apple')}'); print('First element: ${unmodifiableFruits.first}'); // Write operations fail try { unmodifiableFruits.add('pear'); } catch (e) { print('Error when adding: $e'); } }
UnmodifiableSetView provides a convenient way to create unmodifiable views of existing Sets. All read operations work normally while write operations throw.
$ dart main.dart Original: {apple, banana, orange} Unmodifiable view: {apple, banana, orange} Contains apple? true First element: apple Error when adding: Unsupported operation: Cannot modify an unmodifiable set
Using with Set Operations
UnmodifiableSetMixin supports all non-modifying Set operations like union.
import 'dart:collection'; void main() { var set1 = UnmodifiableSetView({1, 2, 3}); var set2 = UnmodifiableSetView({3, 4, 5}); print('Union: ${set1.union(set2)}'); print('Intersection: ${set1.intersection(set2)}'); print('Difference: ${set1.difference(set2)}'); // The results are regular Sets, not unmodifiable var union = set1.union(set2); union.add(6); // This works print('Modified union: $union'); }
Set operations return new regular Set instances, not unmodifiable ones. The original Sets remain unchanged and unmodifiable.
$ dart main.dart Union: {1, 2, 3, 4, 5} Intersection: {3} Difference: {1, 2} Modified union: {1, 2, 3, 4, 5, 6}
Implementing Custom Unmodifiable Set
Here's a more complete custom unmodifiable Set implementation.
import 'dart:collection'; class ImmutableSet<E> extends SetBase<E> with UnmodifiableSetMixin<E> { final Set<E> _source; ImmutableSet(Set<E> source) : _source = Set.of(source); @override Iterator<E> get iterator => _source.iterator; @override int get length => _source.length; @override bool contains(Object? element) => _source.contains(element); @override Set<E> toSet() => ImmutableSet(_source); } void main() { var mutableSet = {1, 2, 3}; var immutable = ImmutableSet(mutableSet); print(immutable); mutableSet.add(4); // Doesn't affect immutable set print(immutable); var copy = immutable.toSet(); print('Copy: $copy'); try { immutable.add(5); } catch (e) { print('Error: $e'); } }
This implementation creates a defensive copy of the source Set. Changes to the original Set don't affect the immutable version. The toSet method preserves immutability.
$ dart main.dart {1, 2, 3} {1, 2, 3} Copy: {1, 2, 3} Error: Unsupported operation: Cannot modify an unmodifiable set
Performance Considerations
UnmodifiableSetMixin has minimal performance overhead as it's just a wrapper.
import 'dart:collection'; import 'dart:math'; void main() { var largeSet = Set.from(Iterable.generate(100000, (i) => i)); var unmodifiable = UnmodifiableSetView(largeSet); var random = Random(); var testElement = random.nextInt(100000); // Performance test var stopwatch = Stopwatch()..start(); print('Contains $testElement? ${unmodifiable.contains(testElement)}'); stopwatch.stop(); print('Lookup time: ${stopwatch.elapsedMicroseconds} μs'); // Memory test print('Original Set size: ${largeSet.length}'); print('Unmodifiable view size: ${unmodifiable.length}'); }
The example demonstrates that lookup performance is identical to the underlying Set. The memory overhead is minimal as it just holds a reference to the original.
$ dart main.dart Contains 73245? true Lookup time: 12 μs Original Set size: 100000 Unmodifiable view size: 100000
Best Practices
- Immutable Data: Use for APIs that shouldn't modify input Sets.
- Defensive Copies: Consider creating copies if source may change.
- Documentation: Clearly document when returning unmodifiable Sets.
- Performance: No significant overhead for read operations.
Source
Dart UnmodifiableSetMixin Documentation
This tutorial covered Dart's UnmodifiableSetMixin with practical examples demonstrating its usage patterns and implementation details.
Author
List all Dart tutorials.