Sign in Join
Wiki copytemporary

TDictionary

TDictionary

TDictionary is a generic data-container for key-value pairs (associative array).

It was introduced with Generics.Collections (rtl-generics) in FPC 3.1.1+ and published in major release 3.2 (June 2020). It's part of the FPC Runtime Library and fully Delphi compatible.

(Like in Delphi) The TDictionary is implemented as a hash table. This means the key-value pairs are not sorted in a defined order in memory and are not iterable by index. However you can iterate over the Key- and Value- collections. Or you can turn them in an array and iterate over that.

Both key and value can be of primitive type (e.g. integer, string, boolean) or rather complex (e.g. classes, records, arrays).

It is recommended to name the specialized dictionary-type with abbreviations of key and value types e.g "TStringIntegerDict" when the key is a of type string and the value is of type integer.

Basic example

pascal
{$mode objfpc} {$H+}
    program Example;
    
    uses
      Generics.Collections;
    
    type
      TStrStrDictionary = specialize TDictionary<string, string>;
    
    var
      MyDictionary: TStrStrDictionary;
    begin
      MyDictionary := TStrStrDictionary.Create;
      try
        //do stuff with your dictionary..
        MyDictionary.AddOrSetValue('Key1', 'Value1');
        
        if MyDictionary.ContainsKey('Key1') then
            WriteLn(MyDictionary['Key1']);
    
      finally
        MyDictionary.Free;
      end;
    end.

See also #Example in Delphi mode.

Usage:

Add

AddOrSetValue() is recommended, because Add() will fail, if the key already exists: 'Dublicate not allowed in Dictionary'.

Add

pascal
MyDictionary.Add('Key1', 'Value1');

AddOrSetValue

recommended

pascal
MyDictionary.AddOrSetValue('Key1', 'Value1');

Remove

pascal
MyDictionary.Remove('Key2');

If the Key does not exist, no Error is thrown.

Clear

Remove all entries.

pascal
MyDictionary.Clear;

Get Value

pascal
WriteLn(MyDictionary['Key1']);
    
    //WriteLn(MyDictionary.Items['Key1']); works aswell

If the provided key does not exist, this will throw an Error: 'Dictionary Key does not Exist'. You could check with .ContainsKey() beforehand or use TryGetValue().

Item Count

Total number of Items in dictionary (count)

pascal
WriteLn('Items in Dictionary: ', MyDictionary.Count);

Loops

Loop Keys

pascal
for KeyStr in MyDictionary.Keys do
      WriteLn('Found key:', KeyStr);

Loop Values

pascal
for ValueStr in MyDictionary.Values do
      WriteLn('Found value: ', ValueStr);

Loop Key-Value-Pairs

pascal
//var KeyValuePair : TStrStrDictionary.TDictionaryPair;
    for KeyValuePair in MyDictionary do
      WriteLn('Found a Pair: Key:' , KeyValuePair.Key , ' Value:' , KeyValuePair.Value);

Check If Key/Value Exists

pascal
//Check if Key exists
    if MyDictionary.ContainsKey('Key1') then
      WriteLn('key exists')
    else
      WriteLn('key not found');
    
    
    //Check if Value exists
    if MyDictionary.ContainsValue('Searched value') then
      writeln('value exists')
    else
      writeln('value not found');

Trying

These functions return a boolean.
True if sucessfull, False if not. No error is thrown, so they are a good alternative to Get Value or Add().

TryAdd

pascal
if MyDictionary.TryAdd('Key','TestValue') then
      writeln('added successfully')
    else
      writeln('not successfull');

Limitation: TryAdd does not update an existing keys value. Use AddOrSetValue() instead. (Even tho there is no feedback provided)

TryGetValue

TryGetValue takes two Arguments ->(key, value) and returns a boolean.
If found, it returns True and the provided 'value' variable becomes the value.
If not found, the function returns False and the provided 'value'-variable is nil.

pascal
//var SearchedValue: string;
    if MyDictionary.TryGetValue('Key 1', SearchedValue) then
      WriteLn('Key found. Its value is: ' + SearchedValue)
    else
      WriteLn('Could not get value');
    
    //writeln(BoolToStr(MyDictionary.TryGetValue('Key 1', SearchedValue), true));

ToArray

ToArray method returns an array of type TDictionaryPair:

pascal
var 
      myArray: array of TStrStrDictionary.TDictionaryPair;
    begin
      myArray := MyDictionary.ToArray()
      WriteLn(length(myArray));
      WriteLn(myArray[0].Key)
      WriteLn(myArray[0].Value)

Get an array of all keys, i.e. turn the Keys collection into an array:

pascal
var 
      keyArray: array of string; //needs to be the correct type
      i: integer;
    begin
      keyArray := MyDictionary.Keys.ToArray()
      WriteLn(length(keyArray));
      WriteLn(keyArray[0])
      for i := 0 to Length(keyArray)-1 do
        writeln(keyArray[i]);

Get an array of all values, i.e. turn the Values collection into an array:

pascal
var 
      valueArray: array of string; //needs to be the correct type
      i: integer;
    begin
      valueArray := MyDictionary.Values.ToArray()
      WriteLn(length(valueArray));
      WriteLn(valueArray[0])
      for i := 0 to Length(valueArray)-1 do
        writeln(valueArray[i]);

Event notifications

You can set up custom functions/procedures which are triggered if an Key or Value got added/removed. To do this, we add 2 procedures to the class we specialize from TDictionary. The TDictionary class provides the events OnKeyNotify and OnValueNotify. We assign them to our procedures.

Hint: If you update a existing value, there is no "updated" event. Instead "removed" followed by "added" is triggered.

TODO: get the Key name inside the ValueNotify event context.

pascal
{$mode objfpc} {$H+}
    program Example;
    
    uses
      Generics.Collections;
    
    type
      TStrStrDictionary = class(specialize TDictionary<ShortString, ShortString>)
      private
        procedure DoKeyNotify(ASender: TObject; constref AItem: ShortString; AAction: TCollectionNotification);
        procedure DoValueNotify(ASender: TObject; constref AItem: ShortString; AAction: TCollectionNotification);
      end;
    
    procedure TStrStrDictionary.DoKeyNotify(ASender: TObject; constref AItem: ShortString; AAction: TCollectionNotification);
    begin
      //WriteLn(AAction);
      //AAction can be cnRemoved or cnAdded
      if AAction = cnAdded then
        writeln('A Key got added, the Key is:', AItem)
      else if AAction = cnRemoved then
        writeln('A Key got removed, the Key was:', AItem)
    end;
    
    procedure TStrStrDictionary.DoValueNotify(ASender: TObject; constref AItem: ShortString; AAction: TCollectionNotification);
    begin
      //how can we get here the key this value was added to?
    
      //Writeln(AAction);
      //can be cnRemoved or cnAdded
      if AAction = cnAdded then
        writeln('A Value got added, the value is:', AItem)
      else if AAction = cnRemoved then
        writeln('A Value got removed, the value was:', AItem)
    end;
    
    
    var
      MyDictionary: TStrStrDictionary;
      TestArr: array of TStrStrDictionary.TDictionaryPair;
    begin
      MyDictionary := TStrStrDictionary.Create;
    
      //Event notifications:
      MyDictionary.OnKeyNotify := @MyDictionary.DoKeyNotify;
      MyDictionary.OnValueNotify := @MyDictionary.DoValueNotify;
    
      try
        MyDictionary.AddOrSetValue('Key1', 'Value1'); //triggers 'Add' Event
        MyDictionary.AddOrSetValue('Key1', 'New Value'); //triggers 'Remove' and 'Add' Event
        MyDictionary.Remove('Key1'); //triggers 'Remove' Event
        Readln();
      finally
        MyDictionary.Free;
      end;
    end.

Using Classes

You can use classes for Keys and/or Values. Because of the fact that all created classes must be manually freed, it is strongly recommended to use a TObjectDictionary instead.
A TObjectDictionary takes ownership of its contained classes and frees them automaticly if its freed.
However here is an example where manual freeing is required:

pascal
{$mode objfpc} {H+}
    program example;
    
    uses
      Generics.Collections;
    
    type
      TMyTestClass = class
        Name: string;
      end;
    
      TIntClassDictionary = specialize TDictionary<Integer, TMyTestClass>;
    
    var
      MyDictionary: TIntClassDictionary;
      MyClass: TMyTestClass;
      i: integer;
    begin
      MyDictionary := TIntClassDictionary.Create;
      try
        for i:= 0 to 5 do
          begin
             MyClass := TMyTestClass.Create;
             MyClass.Name := 'Something';
             MyDictionary.Add(i, MyClass);
          end;
    
         //...
    
      finally
        //manual freeing
        for MyClass in MyDictionary.Values do
          MyClass.Free;
    
        MyDictionary.Free;
      end;
    end.

Exceptions

To handle exceptions, import the SysUtils unit.

pascal
{$mode objfpc} {H+}
    program Example;
    
    uses
      ..., SysUtils;
    
      try          
        WriteLn(MyDictionary.Items['KeyXY']); // Accessing a non-existent key 
      except
        on E: Exception do begin
          WriteLn('An exception was raised: ', E.Message);
          WriteLn(E.ClassName);
        end;
      end;
    
    //Output:
    // An exception was raised: Dictionary key does not exist
    //EListError

Example in Delphi mode

pascal
{$mode delphi}
    uses generics.collections;
     
    type
      TStrIntDict = TDictionary<string, integer>;
     
    var
      MyDict: TStrIntDict;
    begin
      MyDict := TStrIntDict.create;
      MyDict.AddOrSetValue('foo', 42);
      if MyDict.ContainsKey('foo') then
        WriteLn(MyDict['foo']);
    end.

From the Free Pascal wiki, page "TDictionary", as the Internet Archive kept it on 2025-02-14, last changed on 2025-01-06. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

Revision 1, last changed by WikiArchive .

Talk about this page

Nobody has opened a bench for this page yet. Sign in to open one.