I have an object that is derived from the TStringList object that I call a "TAutoString." It allows you to specify an object type when the list is created. Then each time a new entry is added to the string list, it also creates a copy of the object associated with that string entry. This makes it easy to store all kinds of additional information along with each string. For example:
type TMyObject = class(TObject)
public
Cats: integer;
Dogs: integer;
Mice: integer;
end;
MO := TAutoString.Create(TMyObject);
Inside the object, the class information is stored in a class variable:
private
ObjectClass: TClass;
constructor TAutoString.Create(ObjClass: TClass);
begin
inherited Create;
ObjectClass:=ObjClass;
end;
Now, every time a new item is added, it creates a new object of the specified type:
function TAutoString.Add(const S: string): Integer;
begin
Result:=inherited Add(S);
Objects[Result]:=ObjectClass.Create;
end;
I can now add or read information associated with each string entry.
TMyObject(MO.Objects[25]).Cats := 17;
D:=TMyObject(MO.Objects[25]).Dogs;
This works great as along as the object doesn't have a constructor. If the object has a constructor, its constructor won't get called when the object is created because the constructor for TObject is not virtual.
Can anyone think of a way around this problem. I've seen solutions that use the RTTI libraries, but this is in Delphi-7, which doesn't have an RTTI library.
As an aside, it seems a bit strange that TObject's constructor is not virtual. If it were, it would enable all sorts of useful features like the one I'm trying to implement.
EDIT: Remy's suggestion below was just the nudge I needed. I had originally tried a similar strategy, but I couldn't make it work. When it didn't seem to work the way I thought it should, I assumed there must be something that I didn't understand about virtual methods. His post pushed me to look at it again. It turned out that I had left off the "Override" directive for the constructor of the object I wanted to attach. Now it works just the way it should.
The other issue I was concerned about was that I had already used the Auto Strings in a bunch of other applications where the object was based on "TObject" and I didn't want to go back and change all that code. I solved that issue by overloading the constructors and having one for TObject-based objects and another my TAutoClass objects:
constructor Create(ObjClass: TAutoClass); overload; virtual;
constructor Create(ObjClass: TClass); overload; virtual;
Depending on which constructor is called, the object class stored in a different in a different variable.
private
AutoClass: TAutoClass;
ObjectClass: TClass;
Then when the object is constructed I check to see which has been assigned and use that one:
procedure TAutoString.CreateClassInstance(Index: integer);
begin
if AutoClass<>nil then Objects[Index]:=AutoClass.Create
else Objects[Index]:=ObjectClass.Create
end;
The new version works perfectly with either type of object.
To do what you want, you will have to define a base class for your list objects to derive from, and then you can add a virtual constructor to that class. Your ObjectClass
member will have to use that class type instead of using TClass
.
For example:
type
TAutoStringObject = class(TObject)
public
constructor Create; virtual;
end;
TAutoStringObjectClass = class of TAutoStringObject;
TAutoString = class(TStringList)
private
ObjectClass: TAutoStringObjectClass;
public
constructor Create(ObjClass: TAutoStringObjectClass);
function Add(const S: string): Integer; override;
...
end;
...
constructor TAutoStringObject.Create;
begin
inherited Create;
end;
constructor TAutoString.Create(ObjClass: TAutoStringObjectClass);
begin
inherited Create;
ObjectClass := ObjClass;
end;
function TAutoString.Add(const S: string): Integer;
var
Obj: TAutoStringObject;
begin
Obj := ObjectClass.Create;
try
Result := inherited AddObject(S, Obj);
except
Obj.Free;
raise;
end;
end;
...
Then, you simply adjust your derived object classes to use TAutoStringObject
instead of TObject
, eg:
type
TMyObject = class(TAutoStringObject)
public
...
constructor Create; override;
end;
MO := TAutoString.Create(TMyObject);
...
And their constructor will be called, as expected.