Dart Generics


Generics in Dart

Generics in Dart allow you to create classes, methods, and collections that operate on a specific data type but can be used with different data types without needing to rewrite the code. By using generics, you make your code more flexible, reusable, and type-safe. Generics are especially useful for creating collections (e.g., List, Set, Map) and generic classes that work with any data type.

Why Use Generics?

  1. Type Safety: Generics enforce type safety, helping catch type-related errors at compile time.
  2. Code Reusability: Write a single implementation that works with various types.
  3. Clarity and Readability: Generics make it clear what types a class or function will work with, reducing ambiguity.

Basic Syntax of Generics

To declare generics, use angle brackets <> with a type parameter. The most common type parameter name is T (standing for "Type"), but you can use any identifier.

class ClassName<T> { // T can be used as a type within this class }

Examples of Generics in Dart

1. Generic Class Example: Box

A Box class that can hold any type of item, represented by the generic type T. The type is defined when creating an instance of the Box class.

class Box<T> { T item; Box(this.item); void displayItem() { print("Item: $item"); } } void main() { // Create a Box that holds a string var stringBox = Box<String>("Hello"); stringBox.displayItem(); // Output: Item: Hello // Create a Box that holds an integer var intBox = Box<int>(42); intBox.displayItem(); // Output: Item: 42 }

Explanation:

  • The Box class is a generic class that takes a type T.
  • item is a variable of type T, allowing it to hold any type of data.
  • When creating instances of Box, you specify the type. For stringBox, T is String; for intBox, T is int.

2. Generic Method Example

Generics can also be applied to methods, allowing a method to operate on various data types.

void printItem<T>(T item) { print("Item: $item"); } void main() { printItem<String>("Hello, Dart!"); // Output: Item: Hello, Dart! printItem<int>(100); // Output: Item: 100 }

Explanation:

  • printItem is a generic method with the type parameter <T>.
  • It accepts a parameter of type T, allowing it to print any type of item.
  • The method is called with different types (String and int), demonstrating its flexibility.

3. Generic Collections

Collections in Dart, such as List, Set, and Map, are often used with generics to enforce type safety.

void main() { // List of integers List<int> numbers = [1, 2, 3, 4, 5]; print("Numbers: $numbers"); // Map with string keys and integer values Map<String, int> ages = {"Alice": 30, "Bob": 25}; print("Ages: $ages"); }

Explanation:

  • List<int> specifies that numbers can only hold integers.
  • Map<String, int> specifies that ages is a map with String keys and int values.
  • Using generics with collections helps ensure that only the specified types are added, preventing runtime errors.

4. Generic Constraints

You can set constraints on generics to ensure they meet certain requirements. For example, you can restrict a generic type to be a subclass of a particular class.

class Animal { void makeSound() => print("Some animal sound"); } class Dog extends Animal { @override void makeSound() => print("Bark"); } class AnimalShelter<T extends Animal> { T animal; AnimalShelter(this.animal); void showAnimalSound() { animal.makeSound(); } } void main() { var dogShelter = AnimalShelter<Dog>(Dog()); dogShelter.showAnimalSound(); // Output: Bark // var stringShelter = AnimalShelter<String>("Hello"); // Error: String is not a subtype of Animal }

Explanation:

  • AnimalShelter is a generic class that only accepts types that are subclasses of Animal, thanks to <T extends Animal>.
  • The Dog class is a subclass of Animal, so AnimalShelter<Dog> is valid.
  • If you try to create AnimalShelter<String>, it will cause a compile-time error, ensuring type safety.

Benefits of Generics

  1. Reduced Code Duplication: Generics eliminate the need to write multiple versions of a class or function for different data types.
  2. Compile-Time Type Checking: Generics allow Dart to enforce type checks at compile time, reducing runtime errors.
  3. Increased Code Flexibility: Generic classes and functions are flexible and reusable across various data types, making them ideal for many applications.

Summary

Generics in Dart provide a way to create flexible, reusable, and type-safe code. By defining classes, methods, and collections with generic types, Dart allows you to work with multiple data types while enforcing type checks at compile time. Generics are especially powerful when building classes or methods that can handle different data types without sacrificing type safety.