Dart Generics

Summary: in this tutorial, you’ll learn how to use the Dart generics to develop generic classes and methods that work with more than one type.

Introduction to the Dart Generics

Suppose you need to develop a class that represents a pair of values with the same type, for example, a pair of integers or a pair of strings.

To do that, you need to define different classes, one for a pair of integers and another for a pair of strings. For example:

class PairInt {
  int x;
  int y;
  PairInt(this.x, this.y);
}

class PairString {
  String x;
  String y;
  PairString(this.x, this.y);
}Code language: Dart (dart)

This solution is not scalable especially when you want to have pairs of various types. To resolve this, Dart comes up with the concept of generics.

Generics allow you to parameterize the types for functions, classes, and methods.

For example, the following defines a class that represents a pair of values with the same type:

class Pair<T> {
  T x;
  T y;
  Pair(this.x, this.y);
}Code language: Dart (dart)

The letter T inside the angle brackets <> is the type. By convention, type variables have single-letter names like T, E, S, K, and V.

To create a pair of integers, you specify int as the type T when creating a new Pair object:

var pairInt = Pair<int>(10, 20);
print('x=${pairInt.x},y=${pairInt.y}');Code language: Dart (dart)

Output:

x=10,y=20Code language: Dart (dart)

Similarly, to create a pair of strings, you specify the String as the type:

var pairStr = Pair<String>('A', 'B');

print('x=${pairStr.x},y=${pairStr.y}');Code language: Dart (dart)

Output:

x=A,y=BCode language: Dart (dart)

Here’s the complete program:

class Pair<T> {
  T x;
  T y;
  Pair(this.x, this.y);
}

void main() {
  var pairInt = Pair<int>(10, 20);
  print('x=${pairInt.x},y=${pairInt.y}');

  var pairStr = Pair<String>('A', 'B');
  print('x=${pairStr.x},y=${pairStr.y}');
}Code language: Dart (dart)

In fact, you can use the Pair<T> class to represent a pair of any object.

In the core library, Dart uses the generics for the collections like List<T>, Set<T>, and Map<T>.

Parameterized type constraints

When you use type T in a generic class, T is a subtype of the Object type. It means that you can access only methods and properties of the Object type in the generic class.

To specify that T is a subtype of a particular type, you can use the extends keyword like this:

class GenericClass<T extends MyClass> {
   // ...
}Code language: Dart (dart)

In this syntax, T is a subtype of the MyClass instead of Object. Therefore, you can access the methods and properties of the MyClass for any variable declared as T. For example:

abstract class Shape {
  double get area;
}

class Circle extends Shape {
  double radius;

  Circle({required this.radius});

  @override
  double get area => 3.14 * radius * radius;
}

class Square extends Shape {
  double length;
  Square({required this.length});

  @override
  double get area => length * length;
}

class Region<T extends Shape> {
  List<T> shapes;
  Region({required this.shapes});

  double get area {
    double totalArea = 0;
    for (var shape in shapes) {
      totalArea += shape.area;
    }
    return totalArea;
  }
}

void main() {
  var region = Region(
      shapes: [Circle(radius: 10), 
               Square(length: 10), 
               Square(length: 10)]
  );
  
  print(region.area);
}Code language: Dart (dart)

Output:

514.0Code language: Dart (dart)

How it works.

First, define the Shape abstract class and the Circle and Square classes that inherit from the Shape class:

abstract class Shape {
  double get area;
}

class Circle extends Shape {
  double radius;

  Circle({required this.radius});

  @override
  double get area => 3.14 * radius * radius;
}

class Square extends Shape {
  double length;
  Square({required this.length});

  @override
  double get area => length * length;
}Code language: Dart (dart)

Second, define the Region generic class with the type T as the subtype of the Shape class. Inside the Region class, the area getter returns the total area by adding up all the area of all the shapes from the shapes list:

class Region<T extends Shape> {
  List<T> shapes;
  Region({required this.shapes});

  double get area {
    double totalArea = 0;
    for (var shape in shapes) {
      totalArea += shape.area;
    }
    return totalArea;
  }
}Code language: Dart (dart)

Third, create a new Region object and display its total area in the main() function:


void main() {
  var region = Region(
      shapes: [Circle(radius: 10), 
               Square(length: 10), 
               Square(length: 10)]
  );
  
  print(region.area);
}Code language: Dart (dart)

Summary

  • Use generics to define classes and methods that work with more than one type.
  • Use extends to constraint the type of the generics.
Was this tutorial helpful ?