Öffnen Sie fast irgendein ernsthaftes Delphi-Projekt, und Sie finden überall dieselben zwei Freunde: TList<T> und TDictionary<T, V>. Sie sind gut. Sie haben sich ihren Platz verdient. Für einen riesigen Anteil der täglichen Arbeit sind sie wirklich alles, was Sie brauchen.
Doch hin und wieder stoßen Sie auf eine Problemform, für die die Runtime Library keinen Container hat — Sie möchten einen Schlüssel, der auf viele Werte abbildet, oder ein Dictionary, in dem Sie von beiden Seiten nachschlagen können, oder eine Queue, die still ihren ältesten Eintrag verwirft, wenn sie voll ist, oder Sie möchten einfach people.Where(...).OrderBy(...) schreiben, wie Sie es in C# oder LINQ tun würden, und weitermachen. Das ist der Moment, in dem viele von uns zu Spring4D greifen — einer Open-Source-Basisbibliothek (Apache-2.0) von Stefan Glienke, die seit 2009 still und stetig gereift ist und deren Spring.Collections.*-Units, meiner ehrlichen Meinung nach, zu dem Code gehören, mit dem man im gesamten Delphi-Ökosystem am angenehmsten arbeitet.
Ich möchte Ihnen die Tour geben, die ich mir vor Jahren gewünscht hätte: die ganze Breite dessen, was in diesem Werkzeugkasten steckt, mit echtem Code — und dann, genauso ehrlich, wo die moderne Delphi-RTL so gut aufgeholt hat, dass Sie die Abhängigkeit vielleicht gar nicht brauchen. Beides ist gleichzeitig wahr, und genau das macht den Reiz aus.
Alles im Folgenden ist im tatsächlichen Spring4D-Quellcode verankert (die Unit Spring.Collections und ihre Geschwister, Copyright 2009–2024). Wo ich eine Zahl oder eine Behauptung nenne, verlinke ich sie. Wo ich Ihnen meine Meinung gebe, sage ich es dazu.
Zuerst das, was den Knoten platzen lässt: Alles ist ein Interface
Vor jeder konkreten Collection steht eine Designentscheidung, die das gesamte Erlebnis prägt — deshalb zeige ich sie, bevor ich irgendetwas anderes zeige.
In Spring4D erzeugen Sie keine Klasse, die Sie mit Free freigeben müssen. Sie bitten eine Factory um ein Interface, und Delphis Referenzzählung räumt für Sie auf, sobald es den Gültigkeitsbereich verlässt.
uses
Spring.Collections;
procedure Demo;
var
names: IList<string>; // ein Interface, keine Klasse
begin
names := TCollections.CreateList<string>;
names.Add('Ada');
names.Add('Grace');
// kein try/finally, kein names.Free — das Interface ist referenzgezählt
end;Diese TCollections-Factory (deklariert in Spring.Collections.pas) ist die Eingangstür zur gesamten Bibliothek: CreateList, CreateDictionary, CreateSet, CreateStack, CreateQueue, CreateDeque, CreateMultiMap und viele mehr, jeweils mit Overloads für Comparer, Anfangswerte und Ownership.
Hier ist das mentale Modell — denn genau dieser Unterschied lässt den Rest mühelos wirken.
Keiner der beiden Ansätze ist „richtig" — es sind unterschiedliche Philosophien, und beide haben treue Anhänger. Die explizite Ownership der RTL ist transparent und hat null Referenzzählungs-Overhead. Spring4Ds Interface-Modell beseitigt eine ganze Kategorie von try/finally-Boilerplate und die Lecks, die entstehen, wenn man es vergisst. Ich persönlich liebe das zweite Modell; die bestehenden Konventionen Ihrer Codebasis zählen hier aber mehr als meine Vorliebe.
Nun eine offene Beobachtung aus Jahren der Arbeit mit Delphi-Teams: Viele langjährige Delphi-Entwickler haben Interfaces schlicht nie zu einem Teil ihres täglichen Werkzeugkastens gemacht. Das ist keine Kritik — Delphis Objektmodell ist auch ohne sie vollkommen produktiv, und eine Menge exzellenter, ausgelieferter Software wurde von Entwicklern geschrieben, die instinktiv zu TObject-Nachkommen und try/finally greifen und nie mehr brauchten. Aber es bedeutet, dass sich Spring4Ds Interface-first-Design anfangs ungewohnt anfühlen kann — gerade weil es auf einem Sprachfeature aufbaut, das viele von uns übersprungen haben. Wenn das auf Sie zutrifft: Der Gewinn, sich mit Interfaces vertraut zu machen, reicht weit über diese eine Bibliothek hinaus — es ist eine der lohnendsten Fähigkeiten im modernen Delphi.
LINQ in Delphi, wirklich
Das Feature, das mich als Erstes überzeugt hat und mich bis heute begeistert: IEnumerable<T> bringt einen vollständigen Satz von Query-Operatoren mit — Sie drücken also aus, was Sie wollen, statt eine Schleife von Hand zu bauen.
Beginnen wir mit dem Alltagsfall: eine Liste filtern, sortieren und ein Feld herausziehen. Von oben nach unten gelesen sagt der Code genau, was er tut.
uses
Spring.Collections;
var
people: IList<TPerson>;
names: IEnumerable<string>;
begin
// ... people befüllen ...
// Erwachsene, nach Name sortiert, projiziert auf nur den Namen
names :=
TEnumerable.Select<TPerson, string>(
TEnumerable.OrderBy<TPerson, string>(
people.Where(function(const p: TPerson): Boolean
begin Result := p.Age >= 18 end),
function(const p: TPerson): string
begin Result := p.Name end),
function(const p: TPerson): string
begin Result := p.Name end);
end;Sobald sich das flüssig liest, folgen die mächtigeren Operatoren demselben Muster. Hier ist Aggregation — Personen nach Stadt gruppieren, um etwa zu zählen, wie viele in jeder wohnen:
var
people: IList<TPerson>;
byCity: IEnumerable<IGrouping<string, TPerson>>;
group: IGrouping<string, TPerson>;
begin
// ... people befüllen ...
byCity :=
TEnumerable.GroupBy<TPerson, string>(people,
function(const p: TPerson): string
begin Result := p.City end);
for group in byCity do
WriteLn(group.Key, ': ', group.Count); // z. B. "Berlin: 12"
end;Die Operatoren verteilen sich auf zwei Zuhause, und es lohnt sich zu wissen, warum — denn das ist ein Fakt der Delphi-Sprache, keine Spring4D-Eigenheit. Methoden, die keinen neuen Typ einführen, liegen direkt auf IEnumerable<T> — Where, Take, Skip, Distinct, Concat, Union, First, Any, All, Min, Max, Sum, Aggregate, ToArray und mehr. Methoden, die einen neuen Ergebnis- oder Schlüsseltyp einführen (Select<T, TResult>, GroupBy, OrderBy, Zip, SelectMany), können in Delphi keine generischen Interface-Methoden sein und leben deshalb als statische Methoden auf TEnumerable. Genau deshalb liest sich Where oben fluent als people.Where(...), während OrderBy, Select und GroupBy als TEnumerable.OrderBy(...) aufgerufen werden. Kennt man diese Aufteilung einmal, überrascht die API nicht mehr.
Die Collections, die die RTL schlicht nicht mitliefert
Hier landet die Werkzeugkasten-Metapher wirklich. Über IList und IDictionary hinaus enthält Spring4D Container-Formen, die kein direktes Äquivalent in System.Generics.Collections haben. Das ist kein Seitenhieb auf die RTL — sie ist eine bewusst schlanke Standardbibliothek — es ist einfach echt mehr Vielfalt.
Lassen Sie mich die Familie ausbreiten, denn sie zusammen zu sehen ist die halbe Miete.
Einige davon sind jeweils einen Satz wert, denn erst der Anwendungsfall lässt sie einleuchten:
IMultiMap— ein Schlüssel, der auf eine Sammlung von Werten abbildet (CreateMultiMap,CreateHashMultiMap,CreateTreeMultiMapsowie sortierte Varianten). Perfekt für „alle Bestellungen dieses Kunden", ohne dass Sie Listen-von-Listen von Hand verwalten.IBidiDictionary— ein bidirektionales Dictionary, das Sie über Schlüssel oder Wert abfragen können. Praktisch für stabile ID-↔-Name-Zuordnungen, bei denen Sie beide Richtungen brauchen.IMultiSet— ein Bag/Zähler, der mitführt, wie oft jedes Element vorkommt. Ein Worthäufigkeitszähler wird zum Zweizeiler.IDequeund die begrenzten/verdrängenden Puffer — doppelseitige Queues und Ringpuffer auf Basis eines gemeinsamen zirkulären Puffers. EineEvictingQueue, die nur die letzten N Log-Zeilen behält, ist genau das, was Sie sonst neu erfinden und subtil falsch machen würden.IRedBlackTreeund sortierte Sets/Dictionaries — echte selbstbalancierende Bäume, wenn Sie geordnete Iteration mit gutem Einfüge-/Nachschlageverhalten brauchen.
Hier der Zähler in der Praxis — genau die Sorte Code, die man mit echtem Vergnügen nicht von Hand schreiben muss:
var
wordCount: IMultiSet<string>;
word: string;
begin
wordCount := TCollections.CreateMultiSet<string>;
for word in Tokenize(document) do
wordCount.Add(word);
// wordCount['the'] ist jetzt die Anzahl der Vorkommen von 'the'
end;Lebensdauer, Reihenfolge und Events, die Sie gratis bekommen
Ein paar weitere Annehmlichkeiten runden ab, warum sich der Werkzeugkasten stimmig anfühlt statt wie ein Haufen Einzelteile — lassen Sie mich sie gruppieren.
Erstens: Die Einfügereihenfolge bleibt standardmäßig erhalten. TCollections.CreateDictionary<TKey, TValue> liefert tatsächlich ein IOrderedDictionary zurück, sodass die Iteration Ihnen die Einfügereihenfolge zurückgibt — etwas, das das klassische TDictionary der RTL nie versprochen hat.
Zweitens: Änderungsbenachrichtigungen sind eingebaut. Collections stellen ein OnChanged-Event bereit (Dictionaries zusätzlich OnKeyChanged und OnValueChanged), das Aktionen wie caAdded, caRemoved und caReplaced meldet. Es gibt sogar Factories für Observable Lists (CreateObservableList) für UI-artige Binding-Szenarien.
var
items: IList<string>;
begin
items := TCollections.CreateList<string>;
items.OnChanged.Add(
procedure(Sender: TObject; const Item: string;
Action: TCollectionChangedAction)
begin
if Action = caAdded then
Log('added: ' + Item);
end);
items.Add('hello'); // löst den Handler aus
end;Das ist das ganze Verkaufsargument für den Werkzeugkasten: breit, konsistent, referenzgezählt, beobachtbar und schnell (mehr zur Geschwindigkeit gleich). Nun lassen Sie mich genauso begeistert die andere Seite der Geschichte erzählen.
Und wo die moderne RTL alles ist, was Sie brauchen
Hier kommt der Teil, bei dem ich besonders fair sein möchte, denn er ist eine wirklich gute Nachricht für jeden Delphi-Entwickler: Die Standardbibliothek hat einen weiten Weg zurückgelegt, und aktuell zu bleiben zahlt sich auf eine Weise aus, die nicht nur in sichtbaren Komponenten besteht.
Wir neigen dazu, ein neues Delphi-Release danach zu beurteilen, was wir auf ein Formular ziehen können. Aber ein Großteil der wertvollsten Arbeit der letzten Releases fand unter der Haube statt — im Compiler und in der RTL. Die generischen Collections wurden wiederholt verfeinert: Delphi 10.3 Rio fügte TryAdd hinzu, brachte messbare Beschleunigungen bei Kernoperationen wie Add und IndexOf und machte for-in-Schleifen spürbar schneller. Jüngst hat Delphi 12 TOrderedDictionary<K,V> eingeführt und damit einfügegeordnete Iteration direkt in System.Generics.Collections gebracht — eine Fähigkeit, die früher ein Grund war, zu einer Bibliothek zu greifen. Und Delphi 13 Florence (erschienen September 2025) ist die aktuelle, moderne Basis, auf der diese ganze Diskussion steht.
Die ehrliche Zusammenfassung: Die Lücke hat sich stetig geschlossen, Release für Release.
Für einen großen Anteil des Alltagscodes — eine TList<T> von Records, ein TDictionary<Integer, TThing> für Lookups, ein gewöhnliches for-in — gilt also: Die RTL ist kein Kompromiss. Sie ist das richtige Werkzeug, ohne einzige zusätzliche Abhängigkeit. Wenn Ihr Problem diese Form hat, können Sie die Drittbibliothek getrost weglassen und verlieren nichts, was zählt.
Performance: Was tatsächlich bekannt ist — fair, von beiden Seiten
Performance verdient Sorgfalt, denn hier lässt sich am leichtesten übertreiben — also halte ich mich an das, was dokumentiert ist, und kennzeichne den Rest.
Der stärkste veröffentlichte Datenpunkt auf der Spring4D-Seite stammt von seinem Autor Stefan Glienke, der die Neuentwicklung der Collections für Spring4D 2.0 beschreibt: Die 1.x-Collections waren, in seinen eigenen offenen Worten, langsam — "dead slow, especially on Win32" (todlangsam, besonders auf Win32) — weil fast jede Methode virtuell war. Version 2.0 entfernte den virtuellen Dispatch hinter den Interfaces und überarbeitete die Interna, und er berichtet in seinem Benchmark von rund 4–7× Verbesserung gegenüber der RTL bei Dictionary-Lookups. Das ist ein bemerkenswertes Ergebnis — und genau die Art Engineering, die einer Bibliothek ihren Ruf einbringt.
Zwei Portionen Ehrlichkeit gehören allerdings direkt neben diese Zahl.
Die faire Lesart von beiden Seiten lautet: Spring4Ds Collections sind ernsthaft gut optimiert und die RTL hat sich weiter verbessert. Diese beiden Fakten widersprechen sich nicht. Wenn roher Durchsatz auf einer bestimmten Struktur Ihr Engpass ist, lohnt sich Spring4D unbedingt für einen Benchmark — und ebenso lohnt es sich, schlicht auf dem neuesten Delphi zu sein.
Also — wozu greifen Sie?
Lassen Sie mich die Tour zu einer praktischen Entscheidung zurückführen, denn „es kommt darauf an" ist nur nützlich, wenn ich Ihnen sage, worauf es ankommt. Dieser nächste Teil ist meine Meinung, geformt aus Jahren mit beiden; nehmen Sie ihn als Ausgangspunkt, nicht als Regel.
Es gibt hier keine falsche Wahl, und — wichtig — keinen Grund für Dogmatismus. Viele gesunde Codebasen nutzen standardmäßig die RTL und holen Spring4D genau dort hinein, wo eine MultiMap oder ein verdrängender Puffer den Code offensichtlich besser macht. Beides zu mischen ist völlig in Ordnung.
Fazit
Spring.Collections ist ein wirklich reichhaltiger, gut konstruierter Werkzeugkasten, und ich greife nach wie vor gern hinein — aber die moderne Delphi-RTL hat die Lücke so weit geschlossen, dass die richtige Antwort endlich, sauber, eine Frage der Passform ist.
- Die RTL ist exzellent für den Normalfall und wird immer besser —
TryAdd, schnellere Generics und Delphi 12sTOrderedDictionarysind echte Gewinne. Aktuell zu bleiben ist eines der besten Upgrades, das Sie machen können. - Spring4D glänzt, wo die RTL nicht hinreicht: LINQ-artige Abfragen inline, MultiMaps, bidirektionale und sortierte Dictionaries, MultiSets, Deques, verdrängende Ringpuffer, referenzgezählte Lebensdauer und Change-Events — alles hinter einer konsistenten
TCollections-Factory. - Bei der Performance: Respekt für beide. Spring4D 2.0s Neuentwicklung ist beeindruckend schnell (der Autor berichtet 4–7× bei Dictionary-Lookups), und die RTL ist alles andere als langsam — aber nur Ihr Benchmark auf Ihrer Workload sollte über einen Hot Path entscheiden.
- Und wenn der Interface-first-Stil neu für Sie ist: Das ist eine Chance, keine Mauer — ein eigener Beitrag über Interfaces folgt bald, denn sie zahlen sich weit über diese eine Bibliothek hinaus aus.
Zwei großartige Werkzeuge, eine ehrliche Frage: Braucht Ihr Problem etwas, das nur eines von beiden bietet? Wenn ja, wählt das das Werkzeug. Wenn nein, halten Sie es einfach — und so oder so gewinnt Delphi.