TOrderedDictionary in Delphi 12.2 (en)

With Delphi 12.2, an exciting new class has been introduced: TOrderedDictionary. This class provides a useful addition for cases where an ordered dictionary structure was previously missing.

In this post, we will take a look at how TOrderedDictionary works and how to use a custom IComparer to perform a specific sorting.

Why TOrderedDictionary?

Traditional dictionaries like TDictionary in Delphi provide fast access to key-value pairs, but they do not maintain any specific order. This is also completely analogous to standard references on data structures, where dictionaries are usually defined like sets without order.

TOrderedDictionary, however, is designed to maintain the order of elements according to their insertion order, meaning they have an order. Moreover, TOrderedDictionary allows for explicit sorting of both keys and values. You can therefore reorder the collection at will.

Basics of Using TOrderedDictionary

In the following, I have put together a simple example that illustrates how TOrderedDictionary works. In this case, we initialize a dictionary with some sample pairs, sort by different criteria, and use a custom IComparer to order the keys by English number words. The example was originally inspired by Marco’s example, where Marco left the question open as to how one could logically sort the number words he chose.

program OrderedDictionaryDemo;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Generics.Defaults,
  System.Generics.Collections,
  System.Diagnostics;

const
  // String constant for the number words, can be easily extended
  NUMBER_WORDS =
    '''
     one two three four five
     six seven eight nine ten
    ''';

type
  TNumberWordComparer = class(TInterfacedObject, IComparer<string>)
  private
    FNumberWords: TArray<string>;
    function GetNumberValue(const Word: string): Integer;
  public
    constructor Create;
    // Compares two strings based on their numeric value as defined in NUMBER_WORDS
    function Compare(const Left, Right: string): Integer;
  end;

constructor TNumberWordComparer.Create;
begin
  // Split the multiline string into individual words
  FNumberWords := NUMBER_WORDS.Split([sLineBreak, ' ']);
end;

function TNumberWordComparer.GetNumberValue(const Word: string): Integer;
var
  Index: Integer;
begin
  // Find the word in the array and return the index as its value
  Index := TArray.IndexOf<string>(FNumberWords, LowerCase(Word));
  if Index >= 0 then
    Result := Index
  else
    Result := MaxInt; // Return value for words not found
end;

function TNumberWordComparer.Compare(const Left, Right: string): Integer;
begin
  // Compare the two words and return a negative value if
  // the left word-number is smaller than the right one
  Result := GetNumberValue(Left) - GetNumberValue(Right);
end;

begin
  var ODict := TOrderedDictionary<string, string>.Create;
  ODict.Add('one', 'London');
  ODict.Add('two', 'Berlin');
  ODict.Add('three', 'Rome');
  ODict.Add('four', 'Athens');
  ODict.Add('five', 'Madrid');
  ODict.Add('six', 'Bruxelles');
  ODict.Add('seven', 'Paris');

  // Sorting and displaying the elements
  Writeln('Default order:');
  for var APair in ODict do
    Writeln(APair.Key + ' - ' + APair.Value);

  // Sorting by keys and values
  ODict.SortByKeys;
  Writeln('Sorted by keys:');
  for var APair in ODict do
    Writeln(APair.Key + ' - ' + APair.Value);

  ODict.SortByValues;
  Writeln('Sorted by values:');
  for var APair in ODict do
    Writeln(APair.Key + ' - ' + APair.Value);

  // Custom sorting by number words
  ODict.SortByKeys(TNumberWordComparer.Create);
  Writeln('Sorted by number words:');
  for var APair in ODict do
    Writeln(APair.Key + ' - ' + APair.Value);
end.

Features and Flexibility

In our example, we use SortByKeys and SortByValues to sort elements by keys and values. TOrderedDictionary thus offers us a convenient way to change the sorting order as needed.

Of particular interest is the ability to implement a custom IComparer. In this case, we sort by English number words (onetwothree, etc.) using TNumberWordComparer.

The GetNumberValue method searches an array of number words to find the index of a word. This index is used as the numeric value for sorting. If a word is not found, the function returns MaxInt, placing it at the end of the list. The array can be extended as needed. The use of a multi-line string introduced in Delphi 12 makes initializing the array via the Split function easy to maintain.

Important: The comparer is only used when explicitly sorting, not implicitly when new pairs are inserted. Therefore, the dictionary remains a static data structure.

Conclusion

The new TOrderedDictionary class is an interesting addition to the System.Generics.Collections unit in Delphi 12.2. With its ability to maintain the order of insertions and perform flexible sorting, it offers developers a versatile and practical solution for managing key-value pairs.

TOrderedDictionary in Delphi 12.2

Mit Delphi 12.2 wurde eine spannende neue Klasse eingeführt: TOrderedDictionary. Diese Klasse bringt eine sinnvolle Erweiterung, für Fälle wo man bisher eine geordnete Dictionary-Struktur vermisst hat.

 In diesem Beitrag schauen wir uns an, wie TOrderedDictionary funktioniert und wie man mithilfe eines benutzerdefinierten IComparer eine spezielle Sortierung vornimmt.

Warum TOrderedDictionary?

Traditionelle Dictionaries wie TDictionary in Delphi bieten schnellen Zugriff auf Schlüssel-Wert-Paare, aber sie behalten keine bestimmte Reihenfolge bei. Dies ist auch völlig analog zu Standardwerken über Datenstrukturen, wo Dictionaries üblicherweise wie Mengen ohne Ordnung definiert werden.  

TOrderedDictionary hingegen ist darauf ausgelegt, die Reihenfolge der Elemente nach ihrer Einfügereihenfolge beizubehalten, sie haben also eine Ordnung. Darüber hinaus erlaubt TOrderedDictionary eine explizite Sortierung sowohl der Schlüssel als auch der Werte. Man kann also die Ordnung beliebig umsortieren.

Grundlagen der TOrderedDictionary-Verwendung

Im Folgenden habe ich ein einfaches Beispiel zusammengestellt, das die Funktionsweise von TOrderedDictionaryillustriert. In diesem Fall initialisieren wir ein Dictionary mit einigen Beispielpaaren, sortieren nach verschiedenen Kriterien und verwenden einen benutzerdefinierten IComparer, um die Schlüssel nach englischen Zahlenwörtern zu ordnen. Das Beispiel ist ursprünglich von Marcos Beispiel inspiriert, in dem Marco aber die Frage offen lässt, wie man dann tatsächlich die von ihm gewählten Zahlenworte logisch sortieren kann. 

program OrderedDictionaryDemo;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Generics.Defaults,
  System.Generics.Collections,
  System.Diagnostics;

const
  // Stringkonstante für die Zahlenwörter, kann einfach beliebig erweitert werden 
  NUMBER_WORDS =
    '''
     one two three four five
     six seven eight nine ten
    ''';

type
  TNumberWordComparer = class(TInterfacedObject, IComparer<string>)
  private
    FNumberWords: TArray<string>;
    function GetNumberValue(const Word: string): Integer;
  public
    constructor Create;
    // Vergleicht zwei Zeichenfolgen basierend auf ihrem numerischen Wert, wie in NUMBER_WORDS definiert
    function Compare(const Left, Right: string): Integer;
  end;

constructor TNumberWordComparer.Create;
begin
  // Zerlege den Multiline-String in einzelne Wörter
  FNumberWords := NUMBER_WORDS.Split([sLineBreak, ' ']);
end;

function TNumberWordComparer.GetNumberValue(const Word: string): Integer;
var
  Index: Integer;
begin
  // Suche das Wort im Array und gebe den Index als Wert zurück
  Index := TArray.IndexOf<string>(FNumberWords, LowerCase(Word));
  if Index >= 0 then
    Result := Index
  else
    Result := MaxInt; // Rückgabewert für nicht gefundene Wörter
end;

function TNumberWordComparer.Compare(const Left, Right: string): Integer;
begin
  Vergleiche die beiden Worte und gebe einen negativen Wert zurück, wenn
  die linke Wort-Zahl kleiner als die rechte ist.
  Result := GetNumberValue(Left) - GetNumberValue(Right);
end;

begin
  var ODict := TOrderedDictionary<string, string>.Create;
  ODict.Add('one', 'London');
  ODict.Add('two', 'Berlin');
  ODict.Add('three', 'Rome');
  ODict.Add('four', 'Athens');
  ODict.Add('five', 'Madrid');
  ODict.Add('six', 'Bruxelles');
  ODict.Add('seven', 'Paris');

  // Sortieren und Ausgabe der Elemente
  Writeln('Standardreihenfolge:');
  for var APair in ODict do
    Writeln(APair.Key + ' - ' + APair.Value);

  // Sortierung nach Schlüsseln und Werten
  ODict.SortByKeys;
  Writeln('Nach Schlüsseln sortiert:');
  for var APair in ODict do
    Writeln(APair.Key + ' - ' + APair.Value);

  ODict.SortByValues;
  Writeln('Nach Werten sortiert:');
  for var APair in ODict do
    Writeln(APair.Key + ' - ' + APair.Value);

  // Benutzerdefinierte Sortierung nach Zahl-Worten
  ODict.SortByKeys(TNumberWordComparer.Create);
  Writeln('Sortiert nach Zahl-Worten:');
  for var APair in ODict do
    Writeln(APair.Key + ' - ' + APair.Value);
end.

Besonderheiten und Flexibilität

In unserem Beispiel nutzen wir SortByKeys und SortByValues, um Elemente nach Schlüsseln und Werten zu sortieren. TOrderedDictionary bietet uns somit eine bequeme Möglichkeit, die Sortierreihenfolge nach Bedarf zu ändern. 

Besonders interessant ist die Möglichkeit, einen benutzerdefinierten IComparer zu implementieren. In diesem Fall sortieren wir nach englischen Zahlenwörtern (onetwothree usw.) mithilfe von TNumberWordComparer.

Dessen Methode GetNumberValue durchsucht ein Array mit Zahlenwörtern, um den Index eines Worts zu finden. Dieser Index wird als numerischer Wert für die Sortierung verwendet. Falls ein Wort nicht gefunden wird, gibt die Funktion MaxInt zurück, wodurch es am Ende der Liste landet. Das Array lässt sich beliebig erweitern. Die Verwendung eines in Delphi 12 eingeführten Multi-Line-Strings macht das Intialisieren des Arrays über die Split-Funktion leicht wartbar.

Wichtig: Der Comparer wird nur beim konkreten Sortieren verwendet und nicht etwa implizit, wenn neue Paare eingefügt werden. Das Dictionary bleibt also weiterhin eine statische Datenstruktur. 

Fazit

Die neue TOrderedDictionary-Klasse ist eine interessante Erweiterung der System.Generics.Collections-Unit in Delphi 12.2. Mit ihrer Fähigkeit, die Reihenfolge von Einfügungen beizubehalten und eine flexible Sortierung durchzuführen, bietet sie Entwicklern eine vielseitige und praktische Lösung für die Verwaltung von Schlüssel-Wert-Paaren.

Code

Typableitung in generischen Methoden

Durch ein interessantes Code-Beispiel, hinter dem von Kim Madsen zunächst ein möglicher Bug im Delphi Compiler vermutet wurde, habe ich mir das Aufruf-Verhalten von überladenen Methoden, die auch generische Typ Parameter enthalten, genauer angesehen. Ich war von dem möglichen Fehler nämlich überrascht, da ich ziemlich sicher in aktuellen Projekten Overloads mit Kombinationen aus generischen und nicht-generischen Methoden verwende, ohne das offensichtliche Fehler auftreten.

Hier eine vereinfachte Darstellung von Kim’s Code:

TFoo = class(TObject)
  public
    function DoSomething(SomeObject: TValue): string; overload;
    function DoSomething<T>(SomeObject: T): string; overload;
  end;

TBar = class(TObject)
end;

//...

var Foo := TFoo.Create;
var Bar := TBar.Create;

Foo.DoSomething(Bar);

Welche der beiden möglichen Varianten von DoSomething sollte durch Foo.DoSomething(Bar) aufgerufen werden?

Im ersten Ansatz war ich geneigt zu sagen, dass die nicht-generische Variante, also function DoSomething(SomeObject: TValue): string; aufgrufen werden sollte, da ja schliesslich kein Typ-Parameter angegeben ist und daher die erste Variante die am ehesten passende ist.

Tatsächlich wird aber die generische, zweite Variante aufgerufen. Ich hatte zunächst „TValue“ im Verdacht, hier die Overload-Auflösung zu „blockieren“, da TValue ja quasi ein „künstlicher“ RTTI-.getriebener Typ ist.

Stefan Glienke hatte aber letztlich den passenden Hinweis:

Generische Parameter sind tatsächlich optional und der Typ wird durch den Typ der Variablen, die übergeben wird, automatisch abgeleitet. Diese „Type-Inferrence“ hat Vorrang vor dem Auflösen von Overloads und erzeugt damit einen besseren Match als die Methode mit „TValue“.

Die Typableitung bei generischen Methoden ist übrigens hier dokumentiert.

Der folgende Code ruft also tatsächlich eine generische Implementation auf, obwohl kein Typ-Parameter im Aufruf angegeben ist:

function DoSomething<T>(SomeObject: T): string;
//...
Foo.DoSomething(Bar); //<T> is inferred from "Bar"

Hier ein Gist mit komplettem Code zum Ausprobieren:

https://gist.github.com/omonien/24b9b4ba64d46bdfa478615e8e9b0ae6

Embarcadero MVP des Jahres 2019 – DACH Region

Uwe Raabe und ich sind die MVP Koordinatoren der DACH Region und haben u.a. die Aufgabe, einen MVP des Jahres auszuwählen.

Da wir dies nicht irgendwie ausklüngeln wollen, haben wir uns entschlossen, der Delphi Community die demokratische Möglichkeit zu geben, den MVP des Jahres zu wählen.

Auf dieser Seite könnt ihr ab sofort, bis zum kommenden Freitag, den 13. Dezember 2019, eure Stimme für den MVP abgeben, der euch im – schon fast vergangenen – Jahre 2019 am meisten beeindruckt hat, dessen Blogs ihr am informativsten fandet oder von dem ihr einfach denkt, er soll Titel für dieses Jahr erhalten.

Was gewinnt der MVP des Jahres? Ganz viel Ruhm – mit „h“ 🙂

Jim McKeeth, sozusagen der MVP Oberaufseher, wird entsprechende Posts verfassen und den Gewinner damit entsprechend würdigen. Flugreisen, Fernseher oder Millionen von Dollar zu spendieren, davon konnten wir Jim leider nicht überzeugen …

Die Abstimmung erfolgt anonym. Jeder, der sich der Embarcadero Community zugehörig fühlt, hat eine Stimme. MVPs können selbstverständlich mit abstimmen und dürfen sich natürlich auch selbst wählen.

Uwe und ich sind auch wählbar – auch wenn wir uns hier eher als Organisatoren sehen.

Das Ergebnis werden wir nach Ablauf der Abstimmung hier veröffentlichen. Wir werden nur den Sieger benennen, also kein Ranking erstellen.

Update: die Abstimmung ist inzwischen beendet. Das Ergebnis findet sich hier.

4. Deutsche Coderage 26.06.2019 – TMessage vs. MessageDlg

Am 26.06.2019 fand eine weitere Ausgabe der Deutschen CodeRage als Online-Konferenz statt.

Mein Beitrag, der um 17:00 MESZ zu sehen war, behandelt das das Thema TMessage und TMessageDlg: Wie triggert man aus Businesslogik Benutzerabfragen ohne sich an die UI zu koppeln?

TMessage vs MessageDlg

Weiterhin gibt es noch zahlreiche interessante Beiträge meiner Kollegen Matthias Eißing, Dr. Holger Flick, Volker Hillmann, Frank Lauter, Olaf Monien, Uwe Raabe, Bernd Ua und Daniel Wolf.

Die Anmeldung, der Zeitplan und weitere Informationen finden sich auf der CodeRage Seite.

Hier der gezeigte Quelltext, erweitert noch um eine TMessage, die ein einfaches ShowMessage anfordern kann.
Das Video ist auf meinem Youtube-Kanal zu finden.

Signieren von Windows Delphi Anwendungen

FireMonkey Apps für iOS und Android werden in Delphi im Rahmen des Bereitstellungsprozesses automatisch signiert – dies ist inbesondere für iOS Apps unabdingbar und wurde daher in der Delphi IDE auch entsprechend umgesetzt.

Bei Windows Anwendungen (egal ob 32 oder 64 Bit) ist dagegen keine Signatur-Option in Delphi zu finden. Dies dürfte dazu führen, dass viele Delphi Apps unsigniert verteilt werden. Dies ist jedoch spätestens seit Windows 10 nicht mehr zu empfehlen, denn der mit Windows 10 eingeführte „SmartScreen-Filter“ blockiert standardmäßig „nicht vertrauenswürdige“ Anwendungen. Eine unsignierte Anwendung (EXE) ist per se „nicht vertrauenswürdig“, da ihre Herkunft nicht verifiziert werden kann.

Read More

Delphi and Xcode 8.3.x deployment solution

Since Apple updated Xcode to version 8.3.x, iOS IPA deployment is broken with Delphi up to 10.2 (Tokyo). There is an official workaround, which basically instructs to download Xcode 8.2 and use that for now:

http://docwiki.embarcadero.com/PlatformStatus/en/Main_Page#iOS_10

Unfortunately though, Apple started sending out notifications, that it won’t accept any builds created with Xcode versions older than 8.3.2 anymore. This is what I’ve received from Apple a few days ago:

„Dear developer,
We have discovered one or more issues with your recent delivery for „AppEvents“. Your delivery was successful, but you may wish to correct the following issues in your next delivery:
Deprecated Xcode Build – Due to resolved app archives issues, we will be deprecating Xcode 8.3 on May 10, 2017, at which time app archives built with 8.3 will no longer be accepted by the App Store. Download Xcode 8.3.2 or newer, rebuild your app and resubmit.
After you’ve corrected the issues, you can use Xcode or Application Loader to upload a new binary to iTunes Connect.
Regards,
The App Store team“

The problem with Delphi and Xcode 8.3 is basically a tool, that Apple decided to remove from its tool chain, but which Delphi still depends on. So the solution is to copy that missing tool „PackageApp“ from 8.2 to 8.3. Below are the steps that made my Delphi 10.2 (Tokyo) iOS deployment fully working again:

  1. Install current Xcode from Appstore into your applications folder – currently 8.3.2
  2. Download Xcode 8.2.1 from
    https://developer.apple.com/download/more/
  3. Unzip Xcode_8.2.1.xip to a separate folder (e.g. /tmp/Xcode)
  4. Make a backup copy of your current Xcode version:
    cp -R /Applications/Xcode.app /tmp/Xcode.8.3.app (might take a minute or two)
  5. Copy the following file from 8.2.1 to your current Xcode app:
    sudo cp /tmp/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/PackageApplication /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/

Do not forget to add Export/Encryption exemption to your Delphi iOS apps

Apple requires you to specify if your apps are using any form of encryption. If you do not use encryption, you have to confirm that in your app settings on iTunes Connect – this is not new, Apple started asking for that long time ago. Important to notice is though that since a while Apple requires your app to have a special entry in it’s info.plist file – even if do not use any encryption. Without that your app will fail to verify for Testflight and Appstore deliveries.

 

It’s easy to add that entry in Delphi. Go to Project – Options – Version information. The list/grid is actually editable, just go to the last row and press cursor down. Then add „ITSAppUsesNonExemptEncryption – false“ as shown in the screenshot

MVVM Anwendungen mit Delphi und nicht gegen Delphi

Im folgenden Video meines Youtube-Kanals eine Einführung zum Thema MVVM Anwendungen mit Delphi. Wichtige Motivation ist für mich, dass ich soviel Delphi wie möglich verwende, also eben nicht anfange, alles von Hand im Source-Editor hinzuschreiben.

Hinweis: Das Video versteht sich als erste Einführung in das Thema MVVM mit Delphi. Selbstverständlich gibt es noch eine ganze Reihe von Dingen, die man optimieren und ausbauen kann. Weitere Schritte werde ich hier in noch folgenden Beiträgen skizzieren.

Das Video ist in Deutsch.

Wir benutzen Cookies um die Nutzerfreundlichkeit der Webseite zu verbessen. Durch Deinen Besuch stimmst Du dem zu.