Dart

15. Dart Generics

                                         Dart Generics

Generics in Dart allow you to write code that can work with different types without sacrificing type safety. They provide a way to define classes, functions, or methods that operate on a specific type or a group of related types while still maintaining type constraints. Let’s delve into how generics work in Dart with examples:

Basics of Generics

Generics are defined using type parameters enclosed in angle brackets (<>). These type parameters act as placeholders for types that will be specified when instances of generic classes or functions are created.

Example: Generic Class

Here’s how you can define a simple generic class in Dart:

// A generic class Box that can hold any type of value
class Box<T> {
  T value;

  Box(this.value);

  T getValue() {
    return value;
  }
}

void main() {
  // Creating instances of Box with different types
  var intBox = Box<int>(10);
  var stringBox = Box<String>('Hello, Dart!');

  // Using methods of generic class
  int intValue = intBox.getValue();
  String stringValue = stringBox.getValue();

  print('Integer Box value: $intValue'); // Output: Integer Box value: 10
  print('String Box value: $stringValue'); // Output: String Box value: Hello, Dart!
}

In this example:

  • Box<T> is a generic class where T is a type parameter.
  • intBox and stringBox are instances of Box<int> and Box<String>, respectively, specifying T as int and String.
  • The getValue() method returns the stored value of type T.

Constraints on Generics

You can constrain generics to specific types or classes using the extends keyword. This allows you to limit the types that can be used as type arguments.

Example: Generic Class with Type Constraint

// A base class that all types must extend or implement
abstract class BaseClass {
  void display();
}

// A generic class Box that can hold any type of value that extends BaseClass
class Box<T extends BaseClass> {
  T value;

  Box(this.value);

  T getValue() {
    return value;
  }
}

class IntBox extends BaseClass {
  int value;

  IntBox(this.value);

  @override
  void display() {
    print('Integer value: $value');
  }
}

class StringBox extends BaseClass {
  String value;

  StringBox(this.value);

  @override
  void display() {
    print('String value: $value');
  }
}

void main() {
  // Creating instances of Box with different types
  var intBox = Box<IntBox>(IntBox(10));
  var stringBox = Box<StringBox>(StringBox('Hello, Dart!'));

  // Using methods of generic class
  IntBox intValue = intBox.getValue();
  StringBox stringValue = stringBox.getValue();

  print('Integer Box value: ${intValue.value}'); // Output: Integer Box value: 10
  print('String Box value: ${stringValue.value}'); // Output: String Box value: Hello, Dart!

  // Displaying values using the display method
  intValue.display(); // Output: Integer value: 10
  stringValue.display(); // Output: String value: Hello, Dart!
}

In this example:

  • Printer<T extends Printable> restricts T to types that implement the Printable interface.
  • Book implements the Printable interface, defining the printDetails() method.
  • The Printer class prints details of Book instances using the printItem() method.

Using Generic Functions

Dart also allows you to define generic functions, which operate similarly to generic classes but are defined within a function scope.

Example: Generic Function

// A generic function that returns the larger of two values of any comparable type

 getMax<T extends Comparable>(T a, T b) {
  return a.compareTo(b) > 0 ? a : b;
}

void main() {
  // Using the generic function with different types
  int maxInt = getMax<int>(5, 10);
  double maxDouble = getMax<double>(3.5, 1.2);

  print('Max Integer: $maxInt');   // Output: Max Integer: 10
  print('Max Double: $maxDouble'); // Output: Max Double: 3.5
}

Summary

  • Generics in Dart allow you to write reusable code that can work with different types while maintaining type safety.
  • Generic classes and functions are defined using type parameters (<T>), which are replaced with actual types when instances or invocations are made.
  • Constraints (extends) on generics restrict the types that can be used as type arguments, enhancing type safety and code clarity.

Generics are essential for writing flexible and reusable code in Dart, enabling you to create data structures and algorithms that can handle various types of data without compromising type safety or performance.

Leave a Reply

Your email address will not be published. Required fields are marked *