ZetCode

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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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.

main.dart
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

Source

Dart UnmodifiableSetMixin Documentation

This tutorial covered Dart's UnmodifiableSetMixin with practical examples demonstrating its usage patterns and implementation details.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.