Been casting value types to stuff into TStrings.Objects[]?
On numerous occasions I've had the need to store simple, record and interface types in variable or fields of type TObject and had to use some horrible casting to do it. For example, you may resorted to doing stuff like this:
var
SL: TStringList;
Value: Integer;
begin
SL := TStringList.Create;
Value := 42;
SL.AddObject('some text', TObject(5));
...
end;
Not nice!
Things get worse when the size of the data type is greater than SizeOf(TObject)
and this kind of horror is needed:
var
SL: TStringList;
R1, R2: TRect;
PR: PRect;
Idx: Integer;
MyRect: TRect;
begin
...
// add some strings and objects
SL := TStringList.Create;
R1 := Rect(0, 0, 42, 56);
GetMem(PR, SizeOf(TRect));
PR^ := R1;
SL.AddObject('some text', TObject(PR));
R2 := Rect(1, 2, 3, 4);
GetMem(PR, SizeOf(TRect));
PR^ := R2;
SL.AddObject('some more text', TObject(PR));
...
// do something with SL e.g.
MyRect := PRect(SL.Objects[0])^;
...
// free the allocated memory
for Idx := 0 to Pred(SL.Count) do
begin
PR := PRect(SL.Objects[Idx]);
FreeMem(PR, SizeOf(TRect));
end;
SL.Free;
...
end;
And I'm not even going into the reference counting hell that is involved when doing all this with interfaces.
It's common to see this kind of stuff going on when someone wants to associate a value with an item in a list box or a list view for example. Now I know we shouldn't be storing data in UI controls, and I do try to avoid it (honest), but who hasn't done it?!
It many cases the problem can be solved by creating a little class that "wraps" the non-object type:
type
TBox<T> = class(TObject)
strict private
var
fValue: T;
public
constructor Create(Value: T);
property Value: T read fValue;
end;
...
constructor TBox<T>.Create(Value: T);
begin
fValue := Value;
end;
Use TBox<> as follows: here we're wrapping an integer:
var
IntObj: TBox<Integer>;
begin
IntObj := TBox<Integer>.Create(42);
try
...
// use IntObj e.g.
ShowMessageFmt('Value = %d', [IntObj.Value]);
...
finally
IntObj.Free;
end;
end;
Now, the example above using rectangles changes to the somewhat less horrid:
var
SL: TStringList;
Idx: Integer;
MyRect: TRect;
begin
...
// add some strings and objects
SL := TStringList.Create;
SL.AddObject('some text', TBox<TRect>.Create(Rect(0, 0, 42, 56)));
SL.AddObject('some more text', TBox<TRect>.Create(Rect(1, 2, 3, 4)));
...
// do something with SL e.g.
MyRect := (SL.Objects[0] as TBox<TRect>).Value;
...
// free the allocated memory
for Idx := 0 to Pred(SL.Count) do
SL.Objects[Idx].Free;
SL.Free;
...
end;
You still have to do a little casting to get the value and you still need to remember to free the objects, but it's a lot clearer what's happening, and you are actually storing data of the correct type in the TStrings.Objects[] property.
For convenience I've created a Gist containing the TBox<> definition.
Comments
Post a Comment
Comments are very welcome, but please be aware that I moderate all comments, so there will be a delay before your comment appears.
Advertising spam and the rare abusive, hateful or racist comments will be blocked and reported.
Finally, should you have a query about, or a bug report for, one of my programs or libraries please use the relevant issue tracker rather than posting a comment to report it.
Thanks