Dart Record (Datensatz / Tupel)

I. Was ist ein Record?

Ein Record ist ein anonymer, unveränderbarer zusammengesetzter Aggregattyp, mit dem man Daten unterschiedlicher Typen zu einem einzigen Objekt bündelt. Hier sind die zentralen Unterschiede zu List / Set / Map:

  • Feste Länge: Nach Erstellung lässt sich die Anzahl der Felder weder erhöhen noch verringern; heterogene Speicherung:
  • In einem einzelnen Record können völlig verschiedene Typen wie int, String, bool abgelegt werden
  • Starke Typbindung: Der Compiler erkennt den Typ jedes Feldes, Typsicherheit geht nicht verloren
  • Nur lesbar, unveränderlich: Nach Erstellung können Feldwerte nicht neu zugewiesen werden

Ein Record ist eine vollwertige Variable und unterstützt alle üblichen Operationen: Speichern in Variablen, Funktionsparameter / Mehrfachrückgabewerte, Verschachtelung, Einfügen in List/Map/Set.

II. Grundsyntax von Record

1. Record-Literal

Eingeklammert, durch Kommas getrennt, unterteilt in zwei Gruppen: Positionsfelder und benannte Felder. Positionsfelder stehen vorne, benannte Felder folgen mit der Schreibweise Schlüssel:.

// Mischung aus Positionsfeldern und benannten Feldern
var info = ('Jim', age: 20, flag: true, 'Student');Code-Sprache: JavaScript (javascript)

2. Verwendung von Record

1 Positionsfelder

// Deklaration: Zwei Positionsfelder, nacheinander String, int
(String, int) user;

// Zuweisung passend zur Struktur
user = ('李四', 22);
Code-Sprache: JavaScript (javascript)

2 Benannte Felder

Typdeklaration mit geschweiften Klammern {}

// Zwei benannte Felder deklarieren a(int), b(bool)
({int a, bool b}) data;

data = (a: 100, b: false);Code-Sprache: JavaScript (javascript)

3 Gemischte Nutzung

Kombination aus Positionsfeldern und benannten Feldern im Typ festlegen

// 1 String-Positionsfeld, 2 benannte Felder score(int), pass(bool)
(String, {int score, bool pass}) exam;
exam = ('Abschlussprüfung', score: 90, pass: true);
Code-Sprache: JavaScript (javascript)

III. Häufige Fehlerquellen

1: Namen benannter Felder gehören zur Typstruktur; unterschiedliche Namen erzeugen komplett separate Typen
// Typ: ({int a, int b})
({int a, int b}) pointAB = (a: 1, b: 2);
// Typ: ({int x, int y})
({int x, int y}) pointXY = (x: 3, y: 4);

// Kompilierfehler! Typen passen nicht, gegenseitige Zuweisung verboten
// pointAB = pointXY;
Code-Sprache: JavaScript (javascript)

Bei benannten Feldern lassen sich auch bei zwei int-Feldern keine Werte gegenseitig zuweisen, wenn die Feldnamen ab und xy abweichen. Bei Positionsfeldern siehe unten.

2: Aliase bei Positionsfeldern in Typannotationen sind nur Kommentare und beeinflussen den Typabgleich nicht

Namen, die man innerhalb Klammern an Positionsfeldern vergibt, dienen nur als Dokumentation für Entwickler und werden vom Compiler ignoriert. Stimmen die Strukturen überein, ist eine Zuweisung erlaubt:

(int a, int b) p1 = (10, 20);
(int x, int y) p2 = (30, 40);

// Gültig, unter der Haube zwei int-Positionsfelder, Typen identisch
p1 = p2;Code-Sprache: JavaScript (javascript)

Vergleich mit Funktionsparametern: Umbenennen von Parametern ändert nicht die Funktionssignatur, die Logik ist identisch.

IV. Auslesen von Record-Feldern

Records sind unveränderbar, es gibt nur Getter und keine Setter; Feldwerte lassen sich nicht ändern.

  1. Benannte Felder: Direkter Zugriff über .Feldname
  2. Positionsfelder: Zugriff mit $Zahl; die Zahl ignoriert alle benannten Felder und zählt nur die Reihenfolge der Positionsfelder
var record = ('first', a: 2, b: true, 'last');

print(record.$1); // Erstes Positionsfeld: first
print(record.a);  // Benanntes Feld a: 2
print(record.b);  // Benanntes Feld b: true
print(record.$2); // Zweites Positionsfeld: last
Code-Sprache: PHP (php)

V. Regeln zur Erkennung von Record-Strukturtypen

Records benötigen keine separate Klassendefinition; der gleiche Typ wird anhand der Strukturform erkannt:

Form = Gesamtzahl der Felder + Typ jedes Feldes + Namen der benannten Felder

Stimmt die Form vollständig überein, handelt es sich um denselben Typ – auch bei unterschiedlichen Dateien oder Bibliotheken.

Der Compiler erkennt den statischen Typ jedes Feldes dauernd, Typinformationen gehen nicht verloren:

// Position 1: Typ num, Position 2: Typ Object
(num, Object) pair = (42, 'Ein Text');

var firstVal = pair.$1; // Statischer Typ num, Laufzeittyp int
var secondVal = pair.$2;// Statischer Typ Object, Laufzeittyp StringCode-Sprache: JavaScript (javascript)

VI. Gleichheitsvergleich bei Records ==

Zwei Bedingungen müssen erfüllt sein, damit Records gleich sind:

  1. Beide Records haben exakt die gleiche Strukturform
  2. Alle Werte der passenden Positions-/benannten Felder stimmen überein

Die Schreibreihenfolge benannter Felder beeinflusst den Gleichheitsvergleich nicht. Abweichende Feldnamen bedeuten aber automatisch unterschiedliche Typen und damit Ungleichheit.

// Positionsfelder, gleiche Form und übereinstimmende Werte → true
(int x, int y, int z) point = (1, 2, 3);
(int r, int g, int b) color = (1, 2, 3);
print(point == color); // true

// Unterschiedliche Namen bei benannten Feldern, verschiedene Typen → false
({int x, int y, int z}) p = (x:1, y:2, z:3);
({int r, int g, int b}) c = (r:1, g:2, b:3);
print(p == c); // falseCode-Sprache: PHP (php)

Records überschreiben automatisch den Operator == und die Methode hashCode; eine manuelle Implementierung ist nicht nötig.

VII. Mehrfachrückgabewerte von Funktionen

Dart-Funktionen können nur einen einzelnen Wert zurückgeben. Mit Records bündelt man mehrere Daten unterschiedlicher Typen und nutzt die Musterzerlegung, um sie schnell in eigenständige Variablen aufzuteilen. Perfekte Alternative zu List/Map (ohne Verlust der Typsicherheit).

1 Zerlegung von Positionsfeldern

// Rückgabe: Name(String), Alter(int)
(String name, int age) getUserInfo(Map<String, dynamic> json) {
  return (json['name'] as String, json['age'] as int);
}

void main() {
  final userJson = {'name': 'Dash', 'age': 10, 'color': 'blue'};
  // Direkte Zerlegung in einer Zeile, zwei Variablen
  var (userName, userAge) = getUserInfo(userJson);
  print(userName); // Dash
  print(userAge);  // 10
}
Code-Sprache: JavaScript (javascript)

2 Zerlegung benannter Felder

Syntax :Feldname

({String name, int age}) getUserInfo(Map<String, dynamic> json) {
  return (name: json['name'] as String, age: json['age'] as int);
}

void main() {
  final userJson = {'name': 'Dash', 'age': 10};
  // Benannte Zerlegungssyntax
  final (:name, :age) = getUserInfo(userJson);
  print(name);
}
Code-Sprache: JavaScript (javascript)

Nachteile alternativer Lösungen

In anderen Sprachen wie C++, C#, Java ohne Record müssen folgende Umwege genutzt werden:

  1. Neue Klasse erstellen: Überflüssiger Code durch Konstruktor und Felddefinitionen;
  2. Rückgabe per List: Verlust der Typsicherheit, alle Werte werden zu dynamic;
  3. Rückgabe per Map: Tippfehler bei Schlüsseln möglich, keine statische Typprüfung.

VIII. Leichtgewichtige strukturierte Daten

Wenn Sie nur Daten speichern und keine Methoden benötigen, verwenden Sie direkt Records ohne neue Klasse. Das erspart überflüssigen Klassencode und eignet sich hervorragend für Massenlisten von Datensätzen.

Beispiel

Konfigurationsliste für Seitenbuttons (Flutter-Beispiel, verständlich nach dem Erlernen von Flutter)

import 'package:flutter/material.dart';

void main() {
  final buttonList = [
    (
      label: "Datei hochladen",
      icon: const Icon(Icons.upload_file),
      onPressed: () => print("Upload-Button geklickt"),
    ),
    (
      label: "Details anzeigen",
      icon: const Icon(Icons.info),
      onPressed: () => print("Details-Button geklickt"),
    )
  ];
}
Code-Sprache: JavaScript (javascript)

IX. typedef: Typalias für Record-Typen

Lange Typdefinitionen vereinfachen

Record-Typen sind oft sehr lang geschrieben. Mit typedef definieren Sie einen Typalias für wiederholte Nutzung und einfache einheitliche Änderung der Feldstruktur später.

Grundverwendung
// Alias für Button-Record festlegen, onPressed darf null sein
typedef ButtonConfig = ({
  String label,
  Icon icon,
  void Function()? onPressed
});

// Liste direkt mit dem Alias deklarieren
List<ButtonConfig> buttons = [
  (label: "Absenden", icon: const Icon(Icons.check), onPressed: () {}),
  (label: "Abbrechen", icon: const Icon(Icons.close), onPressed: null),
];
Code-Sprache: PHP (php)
Spätere Erweiterung

Wenn Sie später Methoden oder Logik zu den Daten hinzufügen möchten, brauchen Sie den Schleifen-Code zur Darstellung nicht anzupassen – tauschen Sie nur den Typ aus:

  1. Lösung 1: Austausch gegen reguläre Klasse
class ButtonConfig {
  final String label;
  final Icon icon;
  final void Function()? onPressed;
  ButtonConfig({required this.label, required this.icon, this.onPressed});
  // Neue benutzerdefinierte Methode hinzufügen
  bool get hasClickEvent => onPressed != null;
}
Code-Sprache: PHP (php)
  1. Lösung 2: Verpacken des alten Records mit Extension Type
extension type ButtonConfig._(({String label, Icon icon, void Function()? onPressed}) _) {
  String get label => _.label;
  Icon get icon => _.icon;
  void Function()? get onPressed => _.onPressed;

  ButtonConfig({required String label, required Icon icon, void Function()? onPressed})
      : this._((label: label, icon: icon, onPressed: onPressed));

  bool get hasClickEvent => _.onPressed != null;
}
Code-Sprache: PHP (php)

Bei beiden Umstellungsvarianten bleibt der Geschäfts-Code zur Schleifenanzeige der Buttons auf der Seite unverändert.

X. Zusammenfassung

  1. Versionsvoraussetzung: Dart 3.0 oder neuer;
  2. Drei Kernmerkmale: anonym, unveränderbar, aggregierend; feste Länge, heterogene Speicherung, starke Typbindung;
  3. Zwei Feldarten: Positionsfelder $1/$2, benannte Felder .Name;
  4. Typerkennung: Der Typ ergibt sich aus der Strukturform; Namen benannter Felder gehören zur Form, Aliase bei Positionsfeldern sind nur Kommentare;
  5. Gleichheitsvergleich: Gleiche Form + übereinstimmende Werte aller Felder → true;
  6. Hauptvorteile: Mehrfachrückgabewerte bei Funktionen, leichtgewichtiger Datencontainer, vollständige Typsicherheit;
  7. Erweiterungsoptionen: typedef zur Vereinfachung von Typaliasen, später nahtloser Wechsel zu class / Extension Type.
Dart Record (Datensatz / Tupel)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert