views:

588

answers:

3

I tend to use Delphi's TStringList for text manipulation, so I write a lot of procedures/functions like:

var
  TempList: TStringList;
begin
  TempList:= TStringList.Create;
  try
    // blah blah blah do stuff with TempList   


  finally
    TempList.Free;
  end;
end;

It would be nice to cut out the creation and freeing for such a common utility class.

Since we now have records with methods, is it possible to wrap a Class like TStringList in a Record so I could just have:

var
  TempList: TRecordStringList;
begin
  // blah blah blah do stuff with TempList   


end;
+14  A: 

It's possible. Create an interface that exposes the methods / objects you want:

type
  IStringList = interface
    procedure Add(const s: string); // etc.
    property StringList: TStringList read GetStringList; // etc.
  end;

Implement the interface, and have it wrap a real TStringList:

type
  TStringListImpl = class(TInterfacedObject, IStringList)
  private
    FStringList: TStringList; // create in constructor, destroy in destructor
    // implementation etc.
  end;

Then implement the record:

type
  TStringListRecord = record
  private
    FImpl: IStringList;
    function GetImpl: IStringList; // creates TStringListImpl if FImpl is nil
                                   // returns value of FImpl otherwise
  public
    procedure Add(const s: string); // forward to GetImpl.Add
    property StringList: TStringList read GetStringList; // forward to
                                                         // GetImpl.StringList
    // etc.
  end;

The fact that there's an interface inside the record means the compiler will handle reference counting automatically, calling _AddRef and _Release as copies get created and destroyed, so lifetime management is automatic. This works for objects that will never contain a reference to themselves (creating a cycle) - reference counting needs various tricks to get over cycles in the reference graph.

Barry Kelly
Barry: Would using this idea for all data structures in a program be a way to effectively handle garbage collection?
lkessler
No; see the last sentence above, about cycles in the reference graph. Getting around that issue can be very tricky.
Mason Wheeler
That's pretty cool. To clarify, is there a problem only if the object instance contains a reference to it's own instance, or to any instance of the same class?
HMcG
Let's call the record here R, and the instance I. If there is a chain of references, I -> R -> I -> [...] -> I -> R -> I, where any two of the I instances are the same object, then there will be a problem. But this would be a problem with just instances, record wrappers aside, as objects need clear ownership semantics - not everything with a reference to an object can call Free, only the owner. Reference counting - which the record wrapper strategy implements - distributes ownership across all references, the only downside being cycles let objects "own" themselves. Those mustn't use wrappers.
Barry Kelly
There's already IStrings interface declared in StdVCL and TStringsAdapter in AxCtrls which you can reuse for this purpose if you like, instead of reimplementing them as IStringList and TStringListImpl as shown in the example.
TOndrej
+1  A: 

There is another example already implemented in CC.

StringList is the same as TStringList except that it is a value type. It does not have to be created, destroyed or put within try/finally. This is done by the compiler for you. There are virtually no special performance penalties for these to work:

var 
  strings: StringList;
  astr: string;
begin
  strings.Add('test1');
  strings.Add('test2');
  aStr := string(strings);
  RichEdit.Lines.AddStrings(strings);
end;

The code can be used as a template to wrap any TObject as a value class type.

It already has everything for a TStringList exposed for you.

Jim McKeeth
Thanks Jim,I searched both CC and Googled before posting this, and didn't find this one. There are a bunch of utility class objects this would be convenient for. Although I kind of prefer RStringList ;-)
HMcG
+3  A: 

If you are lucky enough to have upgraded to Delphi 2009 then check out Barry's work with smart pointers.

TSmartPointer<T: class> = record
strict private
  FValue: T;
  FLifetime: IInterface;
public
  constructor Create(const AValue: T); overload;
  class operator Implicit(const AValue: T): TSmartPointer<T>;
  property Value: T read FValue;
end;

They are really cool, but require Generics and Anonymous methods. If you haven't upgraded to Delphi 2009, then do it now! Especially while they are offering their BOGO special. You also get Marco's Delphi Developer Handbook free just for downloading the trial. I already purchased a copy of it too.

Jim McKeeth
Already upgraded! Upgraded from Turbo Delphi Pro to Delphi 2009 Professional , so (as far a I am concerned) I got it at a steal. The BOGO offer would have been the icing on the cake, but I am looking at getting Prism as well anyway.
HMcG
Although I have not even touched Generics yet - so much to learn, so little time................
HMcG