Generic types, again introduced in Delphi 2009, allow you to define classes that don't specifically define the type of data members.
A dictionary is, in a way, similar to an array. In an array you would have series (collection) of values indexed by (usually) an integer value (can be any ordinal type value). This index has a lower an an upper bound.
In a dictionary we have keys and values where both keys and values can be of any type.
Hence the declaration of the TDictionary constructor:
In Delphi, the TDictionary is defined as a hash table. Hash tables represents a collection of key-and-value pairs that are organized based on the hash code of the key. Hash tables are optimized for lookups (speed). When a key-value pair is added to a hash table, the hash of the key is computed and stored along with the added pair.
The TKey and TValue (since generics) can be of any type. For example, if the information you are to store in the dictionary is coming from some database, your Key can be a GUID (or some other value presenting the unique index) value while the Value can be an object mapped to a row of data in your database tables.
Using TDictionaryFor the sake of simplicity the example below uses integers for Keys and Chars for Values. Let's see how to do basic operations with dictionaries:
First, we declare our dictionary by specifying what the types of the TKey and TValue will be:// // "log" is a TMemo control placed on a form // var dict : TDictionary<integer, char>; sortedDictKeys : TList<integer>; i, rnd : integer; c : char; begin log.Clear; log.Text := 'TDictionary usage samples'; Randomize; dict := TDictionary<integer, char>.Create; try //add some key/value pairs (random integers, random characters from A in ASCII) for i := 1 to 20 do begin rnd := Random(30); if NOT dict.ContainsKey(rnd) then dict.Add(rnd, Char(65 + rnd)); end; //remove some key/value pairs (random integers, random characters from A in ASCII) for i := 1 to 20 do begin rnd := Random(30); dict.Remove(rnd); end; //loop elements - go through keys log.Lines.Add('ELEMENTS:'); for i in dict.Keys do log.Lines.Add(Format('%d, %s', [i, dict.Items[i]])); //do we have a "special" key value if dict.TryGetValue(80, c) then log.Lines.Add(Format('Found "special", value: %s', [c])) else log.Lines.Add(Format('"Special" key not found', )); //sort by keys ascending log.Lines.Add('KEYS SORTED ASCENDING:'); sortedDictKeys := TList.Create(dict.Keys); try sortedDictKeys.Sort; //default ascending for i in sortedDictKeys do log.Lines.Add(Format('%d, %s', [i, dict.Items[i]])); finally sortedDictKeys.Free; end; //sort by keys descending log.Lines.Add('KEYS SORTED DESCENDING:'); sortedDictKeys := TList.Create(dict.Keys); try sortedDictKeys.Sort(TComparer.Construct( function (const L, R: integer): integer begin result := R - L; end )) ; for i in sortedDictKeys do log.Lines.Add(Format('%d, %s', [i, dict.Items[i]])); finally sortedDictKeys.Free; end; finally dict.Free; end; end;
The dictionary is filled in by using the Add method. Since a dictionary cannot have two pair with the same Key value, you can use the ContainsKey method to check if some key valued pair is already inside the dictionary.dict : TDictionary;
To remove a pair from the dictionary, the Remove method can be used. Note that Remove will not cause problems if a pair with a specified key is not a part of the dictionary.
To go through all the pairs by looping through keys you can do a for in loop.
The TryGetValue method can be used to check if some key value pair is included in the dictionary.
Sorting The DictionarySince a dictionary is a hash table it does not store items in some sort order. If you would like to iterate through the keys that are sorted (in any way you want) you can take advantage of the TList (again a generic collection type) which supports sorting.
The code above sorts keys ascending (and descending) and grabs values as if they were stored in the sorted order in the dictionary. Note that descending sorting of (integer type) Key values uses TComparer and an anonymous method.
When Keys And Values Are Of TObject TypeAs said, the above example is a simple one since both the key and the value are simple types.
Of course, you can have complex dictionaries where both the key and the value would be "complex" types like records or objects.
Here's another example:
Here a custom record is used for the Key and a custom object/class is used for the value.
TMyRecord = record
Name, Surname : string
TMyObject = class(TObject)
Year, Value : integer;
procedure TForm2.logDblClick(Sender: TObject);
dict : TObjectDictionary<TMyRecord, TMyObject>;
myR : TmyRecord;
myO : TMyObject;
dict := TObjectDictionary<TMyRecord, TMyObject>.Create([doOwnsValues]);
myR.Name := 'Zarko';
myR.Surname := 'Gajic';
myO := TMyObject.Create;
myO.Year := 2012;
myO.Value := 39;
myR.Name := 'Zarko';
myR.Surname := '?????';
if NOT dict.ContainsKey(myR) then log.Lines.Add('not found');
Note the usage of a specialized TObjectDictionary class here. TObjectDictionary has the capability to handle objects' lifetime automatically.
The Key value cannot be nil, while the Value value can.
When a TObjectDictionary is instantiated, an Ownerships parameter specifies whether the dictionary owns the keys, values, or both - and therefore helps you not have memory leaks.