Tuesday, September 30, 2008

A simple Generic Dictionary: TDictionary

A new generic type in Delphi 2009 is TDictionary. TDictionary offers a way to store values based on a key into a list. TDictionary is declared as TDictionary<TKey, TValue>.
TDictionary in fact is what a hashtable is in C#. It allows you to store/structure data based on any key type and any value type.

Suppose we want to track our persons, from the previous blogpost, on their social security number, we could put them in TDictionary like this: (using a very simple social security number....)

procedure TForm4.Button2Click(Sender: TObject);
var
Dic : TDictionary
<Integer,TPerson>;
p : TPerson;
i : integer;
begin
//Create dictionary
Dic := TDictionary<Integer,TPerson>.Create;
Dic.Add(
1, TPerson.Create('Delphi', 'Mr'));
Dic.Add(
2, TPerson.Create('Generic', 'Bill'));
Dic.Add(
3, TPerson.Create('nonymous', 'An'));
try

//Travel the strings
for p in Dic.Values do begin
ShowMessage(p.FullName);
end;

//Travel the keys
for i in Dic.Keys do begin
ShowMessage(IntToStr(i)
+ ': ' +
Dic.Items[i].FullName);
end;

//Find some key
if Dic.TryGetValue(3, p) then begin
ShowMessage(
'Found it!: ' + p.FullName);
end;

finally
for p in Dic.Values do
p.Free;

//Also free Values and KeyCollection
//other wise you have a memoryleak
//Is this a bug?
Dic.Values.Free;
Dic.Keys.Free;

//Free the dictionary
Dic.Free;
end;

I noticed that if you free the Dictionary in above scenario you must also free the Values and Keys collection to avoid a memory leak. This looks like a bug to me. (Will investigate this further)

In this sample I used an integer type as the key value, so this has not much benefit compared to an array. You can however use any type to be the key value!

Because we can store any type, we can take this a step further and put a TList<T> in our Dictionary:

Suppose we want to put our Personlist into a dictionary based on, let's say their gender. You could do that something like this:


type
PersonKind
= (pkMale, pkFemale);

procedure TForm4.Button3Click(Sender: TObject);
var
Dic : TDictionary
<PersonKind,TList<TPerson>>;
MPersonsList : TList
<TPerson>;
FPersonsList : TList
<TPerson>;
pList : TList
<TPerson>;
p : TPerson;

begin
MPersonsList :
= TList<TPerson>.Create;
FPersonsList :
= TList<TPerson>.Create;
Dic :
= TDictionary<PersonKind,TList<TPerson>>.Create;
try
//Fill male list
MPersonsList.Add(TPerson.Create('Delphi','Mr'));
MPersonsList.Add(TPerson.Create(
'Generic','Bill'));
MPersonsList.Add(TPerson.Create(
'Nymous','Ano'));
//Fill female list
FPersonsList.Add(TPerson.Create('Delphi','Mrs'));
FPersonsList.Add(TPerson.Create(
'Nymous','Anna'));
//Add to dictionary
Dic.Add(pkMale, MPersonsList);
Dic.Add(pkFemale, FPersonsList);

//Travel
for p in Dic[pkMale] do begin
ShowMessage(
'This is a man: ' + p.FullName);
end;
for p in Dic[pkFemale] do begin
ShowMessage(
'This is a female: ' + p.FullName);
end;
finally
//Free Persons and Personlist
for p in MPersonsList do p.Free;
for p in FPersonsList do p.Free;
MPersonsList.Free;
FPersonsList.Free;

Dic.Free;
end;
end;

TDatadictionary is a very powerfull, yet in basic easy to use, generic type. It allows you to build simple, but also complex data structures. (What about a TDictionary with TDictionary's in it? ;-) )

Again a very nice language addition to Delphi!

7 comments:

Andreas Hausladen said...

Looking at the TDictionary code, it is definitely a memory leak.

Bruce McGee said...

To avoid leaking memory, use the TObjectDictionary descendant, which optionally manages the ownership of keys and/or values. Look at the overloaded constructors with the TDictionaryOwnerships parameter.

Roland Beenhakker said...

@Bruce
Great tip Bruce! Not yet discoverd those.

Thanks!

Andreas Hausladen said...

TObjectDictionary doesn't solve the GetValues and GetKeys memory leak.

Bruce McGee said...

Whoops. It's leaking the collections themselves (as the OP said).

I expect there's already a QC report in for this?

Fedor said...

Are there a way to use RTTI information to create the procedure which will free internal collections of the TDictionary ?

Anonymous said...

Thanks for your example. I just want to say that i do not think this is a memory leak. If you want to use objects in your dictionary you should use a TObjectDictionary class an provide it the ownership of the objects for either values, keys or both.

Thanks!