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 don't comment here if:
1) You have a query about, or a bug report for, one of my programs or libraries. Most of my posts contain a link to the relevant repository where there will be an issue tracker you can use.
2) You have a query about any 3rd party programs I feature, please address them to the developer(s) - there will be a link in the post.
3) You're one of the tiny, tiny minority who are aggressive or abusive - in the bin you go and reported you will be!
Thanks