TDictionary: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: TDictionary
| Old | New | Line | |
|---|---|---|---|
| 1 | + | ## TDictionary | |
| 2 | + | ||
| 3 | + | TDictionary is a generic data-container for key-value pairs (associative array). | |
| 4 | + | ||
| 5 | + | 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. | |
| 6 | + | ||
| 7 | + | (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. | |
| 8 | + | ||
| 9 | + | Both key and value can be of primitive type (e.g. integer, string, boolean) or rather complex (e.g. classes, records, arrays). | |
| 10 | + | ||
| 11 | + | 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. | |
| 12 | + | ||
| 13 | + | ## Basic example | |
| 14 | + | ``` | |
| 15 | + | {$mode objfpc} {$H+} | |
| 16 | + | program Example; | |
| 17 | + | ||
| 18 | + | uses | |
| 19 | + | Generics.Collections; | |
| 20 | + | ||
| 21 | + | type | |
| 22 | + | TStrStrDictionary = specialize TDictionary<string, string>; | |
| 23 | + | ||
| 24 | + | var | |
| 25 | + | MyDictionary: TStrStrDictionary; | |
| 26 | + | begin | |
| 27 | + | MyDictionary := TStrStrDictionary.Create; | |
| 28 | + | try | |
| 29 | + | //do stuff with your dictionary.. | |
| 30 | + | MyDictionary.AddOrSetValue('Key1', 'Value1'); | |
| 31 | + | ||
| 32 | + | if MyDictionary.ContainsKey('Key1') then | |
| 33 | + | WriteLn(MyDictionary['Key1']); | |
| 34 | + | ||
| 35 | + | finally | |
| 36 | + | MyDictionary.Free; | |
| 37 | + | end; | |
| 38 | + | end. | |
| 39 | + | ``` | |
| 40 | + | ||
| 41 | + | See also #Example in Delphi mode. | |
| 42 | + | ||
| 43 | + | ## Usage: | |
| 44 | + | ||
| 45 | + | ### Add | |
| 46 | + | ||
| 47 | + | **AddOrSetValue()** is recommended, because **Add()** will fail, if the key already exists: 'Dublicate not allowed in Dictionary'. | |
| 48 | + | ||
| 49 | + | #### Add | |
| 50 | + | ``` | |
| 51 | + | MyDictionary.Add('Key1', 'Value1'); | |
| 52 | + | ``` | |
| 53 | + | ||
| 54 | + | #### AddOrSetValue | |
| 55 | + | ||
| 56 | + | recommended | |
| 57 | + | ``` | |
| 58 | + | MyDictionary.AddOrSetValue('Key1', 'Value1'); | |
| 59 | + | ``` | |
| 60 | + | ||
| 61 | + | ### Remove | |
| 62 | + | ``` | |
| 63 | + | MyDictionary.Remove('Key2'); | |
| 64 | + | ``` | |
| 65 | + | ||
| 66 | + | If the Key does not exist, no Error is thrown. | |
| 67 | + | ||
| 68 | + | ### Clear | |
| 69 | + | ||
| 70 | + | Remove all entries. | |
| 71 | + | ``` | |
| 72 | + | MyDictionary.Clear; | |
| 73 | + | ``` | |
| 74 | + | ||
| 75 | + | ### Get Value | |
| 76 | + | ``` | |
| 77 | + | WriteLn(MyDictionary['Key1']); | |
| 78 | + | ||
| 79 | + | //WriteLn(MyDictionary.Items['Key1']); works aswell | |
| 80 | + | ``` | |
| 81 | + | ||
| 82 | + | 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(). | |
| 83 | + | ||
| 84 | + | ### Item Count | |
| 85 | + | ||
| 86 | + | Total number of Items in dictionary (count) | |
| 87 | + | ``` | |
| 88 | + | WriteLn('Items in Dictionary: ', MyDictionary.Count); | |
| 89 | + | ``` | |
| 90 | + | ||
| 91 | + | ### Loops | |
| 92 | + | ||
| 93 | + | #### Loop Keys | |
| 94 | + | ``` | |
| 95 | + | for KeyStr in MyDictionary.Keys do | |
| 96 | + | WriteLn('Found key:', KeyStr); | |
| 97 | + | ``` | |
| 98 | + | ||
| 99 | + | #### Loop Values | |
| 100 | + | ``` | |
| 101 | + | for ValueStr in MyDictionary.Values do | |
| 102 | + | WriteLn('Found value: ', ValueStr); | |
| 103 | + | ``` | |
| 104 | + | ||
| 105 | + | #### Loop Key-Value-Pairs | |
| 106 | + | ``` | |
| 107 | + | //var KeyValuePair : TStrStrDictionary.TDictionaryPair; | |
| 108 | + | for KeyValuePair in MyDictionary do | |
| 109 | + | WriteLn('Found a Pair: Key:' , KeyValuePair.Key , ' Value:' , KeyValuePair.Value); | |
| 110 | + | ``` | |
| 111 | + | ||
| 112 | + | ### Check If Key/Value Exists | |
| 113 | + | ``` | |
| 114 | + | //Check if Key exists | |
| 115 | + | if MyDictionary.ContainsKey('Key1') then | |
| 116 | + | WriteLn('key exists') | |
| 117 | + | else | |
| 118 | + | WriteLn('key not found'); | |
| 119 | + | ||
| 120 | + | ||
| 121 | + | //Check if Value exists | |
| 122 | + | if MyDictionary.ContainsValue('Searched value') then | |
| 123 | + | writeln('value exists') | |
| 124 | + | else | |
| 125 | + | writeln('value not found'); | |
| 126 | + | ``` | |
| 127 | + | ||
| 128 | + | ### Trying | |
| 129 | + | ||
| 130 | + | These functions return a boolean. | |
| 131 | + | True if sucessfull, False if not. No error is thrown, so they are a good alternative to Get Value or Add(). | |
| 132 | + | ||
| 133 | + | #### TryAdd | |
| 134 | + | ``` | |
| 135 | + | if MyDictionary.TryAdd('Key','TestValue') then | |
| 136 | + | writeln('added successfully') | |
| 137 | + | else | |
| 138 | + | writeln('not successfull'); | |
| 139 | + | ``` | |
| 140 | + | ||
| 141 | + | Limitation: TryAdd does **not** update an existing keys value. Use AddOrSetValue() instead. (Even tho there is no feedback provided) | |
| 142 | + | ||
| 143 | + | #### TryGetValue | |
| 144 | + | ||
| 145 | + | TryGetValue takes two Arguments ->(key, value) and returns a boolean. | |
| 146 | + | If found, it returns **True** and the provided 'value' variable becomes the value. | |
| 147 | + | If not found, the function returns **False** and the provided 'value'-variable is **nil**. | |
| 148 | + | ``` | |
| 149 | + | //var SearchedValue: string; | |
| 150 | + | if MyDictionary.TryGetValue('Key 1', SearchedValue) then | |
| 151 | + | WriteLn('Key found. Its value is: ' + SearchedValue) | |
| 152 | + | else | |
| 153 | + | WriteLn('Could not get value'); | |
| 154 | + | ||
| 155 | + | //writeln(BoolToStr(MyDictionary.TryGetValue('Key 1', SearchedValue), true)); | |
| 156 | + | ``` | |
| 157 | + | ||
| 158 | + | ### ToArray | |
| 159 | + | ||
| 160 | + | ToArray method returns an array of type TDictionaryPair: | |
| 161 | + | ``` | |
| 162 | + | var | |
| 163 | + | myArray: array of TStrStrDictionary.TDictionaryPair; | |
| 164 | + | begin | |
| 165 | + | myArray := MyDictionary.ToArray() | |
| 166 | + | WriteLn(length(myArray)); | |
| 167 | + | WriteLn(myArray[0].Key) | |
| 168 | + | WriteLn(myArray[0].Value) | |
| 169 | + | ``` | |
| 170 | + | ||
| 171 | + | ||
| 172 | + | ||
| 173 | + | Get an array of all keys, i.e. turn the Keys collection into an array: | |
| 174 | + | ``` | |
| 175 | + | var | |
| 176 | + | keyArray: array of string; //needs to be the correct type | |
| 177 | + | i: integer; | |
| 178 | + | begin | |
| 179 | + | keyArray := MyDictionary.Keys.ToArray() | |
| 180 | + | WriteLn(length(keyArray)); | |
| 181 | + | WriteLn(keyArray[0]) | |
| 182 | + | for i := 0 to Length(keyArray)-1 do | |
| 183 | + | writeln(keyArray[i]); | |
| 184 | + | ``` | |
| 185 | + | ||
| 186 | + | Get an array of all values, i.e. turn the Values collection into an array: | |
| 187 | + | ``` | |
| 188 | + | var | |
| 189 | + | valueArray: array of string; //needs to be the correct type | |
| 190 | + | i: integer; | |
| 191 | + | begin | |
| 192 | + | valueArray := MyDictionary.Values.ToArray() | |
| 193 | + | WriteLn(length(valueArray)); | |
| 194 | + | WriteLn(valueArray[0]) | |
| 195 | + | for i := 0 to Length(valueArray)-1 do | |
| 196 | + | writeln(valueArray[i]); | |
| 197 | + | ``` | |
| 198 | + | ||
| 199 | + | ## Event notifications | |
| 200 | + | ||
| 201 | + | 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. | |
| 202 | + | ||
| 203 | + | Hint: If you update a existing value, there is no "updated" event. Instead "removed" followed by "added" is triggered. | |
| 204 | + | ||
| 205 | + | TODO: get the Key name inside the ValueNotify event context. | |
| 206 | + | ``` | |
| 207 | + | {$mode objfpc} {$H+} | |
| 208 | + | program Example; | |
| 209 | + | ||
| 210 | + | uses | |
| 211 | + | Generics.Collections; | |
| 212 | + | ||
| 213 | + | type | |
| 214 | + | TStrStrDictionary = class(specialize TDictionary<ShortString, ShortString>) | |
| 215 | + | private | |
| 216 | + | procedure DoKeyNotify(ASender: TObject; constref AItem: ShortString; AAction: TCollectionNotification); | |
| 217 | + | procedure DoValueNotify(ASender: TObject; constref AItem: ShortString; AAction: TCollectionNotification); | |
| 218 | + | end; | |
| 219 | + | ||
| 220 | + | procedure TStrStrDictionary.DoKeyNotify(ASender: TObject; constref AItem: ShortString; AAction: TCollectionNotification); | |
| 221 | + | begin | |
| 222 | + | //WriteLn(AAction); | |
| 223 | + | //AAction can be cnRemoved or cnAdded | |
| 224 | + | if AAction = cnAdded then | |
| 225 | + | writeln('A Key got added, the Key is:', AItem) | |
| 226 | + | else if AAction = cnRemoved then | |
| 227 | + | writeln('A Key got removed, the Key was:', AItem) | |
| 228 | + | end; | |
| 229 | + | ||
| 230 | + | procedure TStrStrDictionary.DoValueNotify(ASender: TObject; constref AItem: ShortString; AAction: TCollectionNotification); | |
| 231 | + | begin | |
| 232 | + | //how can we get here the key this value was added to? | |
| 233 | + | ||
| 234 | + | //Writeln(AAction); | |
| 235 | + | //can be cnRemoved or cnAdded | |
| 236 | + | if AAction = cnAdded then | |
| 237 | + | writeln('A Value got added, the value is:', AItem) | |
| 238 | + | else if AAction = cnRemoved then | |
| 239 | + | writeln('A Value got removed, the value was:', AItem) | |
| 240 | + | end; | |
| 241 | + | ||
| 242 | + | ||
| 243 | + | var | |
| 244 | + | MyDictionary: TStrStrDictionary; | |
| 245 | + | TestArr: array of TStrStrDictionary.TDictionaryPair; | |
| 246 | + | begin | |
| 247 | + | MyDictionary := TStrStrDictionary.Create; | |
| 248 | + | ||
| 249 | + | //Event notifications: | |
| 250 | + | MyDictionary.OnKeyNotify := @MyDictionary.DoKeyNotify; | |
| 251 | + | MyDictionary.OnValueNotify := @MyDictionary.DoValueNotify; | |
| 252 | + | ||
| 253 | + | try | |
| 254 | + | MyDictionary.AddOrSetValue('Key1', 'Value1'); //triggers 'Add' Event | |
| 255 | + | MyDictionary.AddOrSetValue('Key1', 'New Value'); //triggers 'Remove' and 'Add' Event | |
| 256 | + | MyDictionary.Remove('Key1'); //triggers 'Remove' Event | |
| 257 | + | Readln(); | |
| 258 | + | finally | |
| 259 | + | MyDictionary.Free; | |
| 260 | + | end; | |
| 261 | + | end. | |
| 262 | + | ``` | |
| 263 | + | ||
| 264 | + | ## Using Classes | |
| 265 | + | ||
| 266 | + | 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. | |
| 267 | + | A [[TObjectDictionary]] takes ownership of its contained classes and frees them automaticly if its freed. | |
| 268 | + | However here is an example where manual freeing is required: | |
| 269 | + | ``` | |
| 270 | + | {$mode objfpc} {H+} | |
| 271 | + | program example; | |
| 272 | + | ||
| 273 | + | uses | |
| 274 | + | Generics.Collections; | |
| 275 | + | ||
| 276 | + | type | |
| 277 | + | TMyTestClass = class | |
| 278 | + | Name: string; | |
| 279 | + | end; | |
| 280 | + | ||
| 281 | + | TIntClassDictionary = specialize TDictionary<Integer, TMyTestClass>; | |
| 282 | + | ||
| 283 | + | var | |
| 284 | + | MyDictionary: TIntClassDictionary; | |
| 285 | + | MyClass: TMyTestClass; | |
| 286 | + | i: integer; | |
| 287 | + | begin | |
| 288 | + | MyDictionary := TIntClassDictionary.Create; | |
| 289 | + | try | |
| 290 | + | for i:= 0 to 5 do | |
| 291 | + | begin | |
| 292 | + | MyClass := TMyTestClass.Create; | |
| 293 | + | MyClass.Name := 'Something'; | |
| 294 | + | MyDictionary.Add(i, MyClass); | |
| 295 | + | end; | |
| 296 | + | ||
| 297 | + | //... | |
| 298 | + | ||
| 299 | + | finally | |
| 300 | + | //manual freeing | |
| 301 | + | for MyClass in MyDictionary.Values do | |
| 302 | + | MyClass.Free; | |
| 303 | + | ||
| 304 | + | MyDictionary.Free; | |
| 305 | + | end; | |
| 306 | + | end. | |
| 307 | + | ``` | |
| 308 | + | ||
| 309 | + | ## Exceptions | |
| 310 | + | ||
| 311 | + | To handle exceptions, import the SysUtils unit. | |
| 312 | + | ``` | |
| 313 | + | {$mode objfpc} {H+} | |
| 314 | + | program Example; | |
| 315 | + | ||
| 316 | + | uses | |
| 317 | + | ..., SysUtils; | |
| 318 | + | ||
| 319 | + | try | |
| 320 | + | WriteLn(MyDictionary.Items['KeyXY']); // Accessing a non-existent key | |
| 321 | + | except | |
| 322 | + | on E: Exception do begin | |
| 323 | + | WriteLn('An exception was raised: ', E.Message); | |
| 324 | + | WriteLn(E.ClassName); | |
| 325 | + | end; | |
| 326 | + | end; | |
| 327 | + | ||
| 328 | + | //Output: | |
| 329 | + | // An exception was raised: Dictionary key does not exist | |
| 330 | + | //EListError | |
| 331 | + | ``` | |
| 332 | + | ||
| 333 | + | ## Example in Delphi mode | |
| 334 | + | ``` | |
| 335 | + | {$mode delphi} | |
| 336 | + | uses generics.collections; | |
| 337 | + | ||
| 338 | + | type | |
| 339 | + | TStrIntDict = TDictionary<string, integer>; | |
| 340 | + | ||
| 341 | + | var | |
| 342 | + | MyDict: TStrIntDict; | |
| 343 | + | begin | |
| 344 | + | MyDict := TStrIntDict.create; | |
| 345 | + | MyDict.AddOrSetValue('foo', 42); | |
| 346 | + | if MyDict.ContainsKey('foo') then | |
| 347 | + | WriteLn(MyDict['foo']); | |
| 348 | + | end. | |
| 349 | + | ``` | |
| 350 | + | ||
| 351 | + | --- | |
| 352 | + | *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](/wiki-contact).* |
