Delphi: how to use TObjectList<T>? - delphi

I need to understand how to use the generic Delphi 2009 TObjectList. My non-TObjectList attempt looked like
TSomeClass = class(TObject)
private
FList1: Array of TList1;
FList2: Array of TList2;
public
procedure FillArray(var List: Array of TList1; Source: TSource); Overload;
procedure FillArray(var List: Array of TList2; Source: TSource); Overload;
end;
Here, TList1 and TList2 inherits the same constructor constructor TParent.Create(Key: string; Value: string);. However, due to different specialization (e.g. different private fields), they will not be of the same type. So I have to write two nearly identical fill methods:
procedure TSomeClass.FillArray(var List: Array of TList1; Source: TSource);
begin
for i := 0 to Source.List1.Count - 1 do begin
SetLength(List, Length(List) + 1);
List[i] := TList1.Create(Source.List1[i].Key, Source.List1[i].Value);
end;
end;
with FillArray(List: Array of TList2; Source: TSource); being identical, except for the replacement of TList1 with TList2 throughout. As far as I understand, this could be neatly circumvented by using TObjectList and a single fill method; yet, I don't have a clue how to go about this. Do anyone have some good pointers on this? Thanks!

You wouldn't be able to condense that down by using a generic list, since a generic's type is part of the class definition. So a TObjectList<TMyClass1> is different from (and incompatible with) a TObjectList<TMyClass2>. The main benefit of using generic lists over normal TList/TObjectList is improved type safety, with less casts and cleaner code.
Also, if you're using key/value pairs, are you putting them into a list and then retrieving them by searching for a key and returning the associated value? If so, take a look at TDictionary in Generics.Collections. It's a generic key/value hash table that will greatly simplify this process for you.

The Official Embarcadero documentation Wiki on the Generics.Collections.TObjectList contains a simple code example of the TObjectList in action.
I'm not certain exactly what the question is driving at but to address the broad use of a TObjectList, the example initialisation code for a TObjectList might look like this:
var
List: TObjectList<TNewObject>;
Obj: TNewObject;
begin
{ Create a new List. }
List := TObjectList<TNewObject>.Create();
{ Add some items to the List. }
List.Add(TNewObject.Create('One'));
List.Add(TNewObject.Create('Two'));
{ Add a new item, but keep the reference. }
Obj := TNewObject.Create('Three');
List.Add(Obj);
The example code should give you an idea of what the TObjectList can do but If I've understood the question correctly it seems that you would like to be able to add more than one class type to a single instance of the TObjectList? A TObjectList can only be initiated with a single type so it might be better if you initiated the TObjectList with a Interface or Abstract class that is shared by all of the classes you wish to add to it.
One important difference when using a TObjectList compared to creating your own is the existance of the OwnsObjects property which tells the TObjectList whether it owns the objects you add to it and therefore consequently whether it should manage freeing them itself.

Something like this?
TSomeClass = class
private
FList1: TArray<TList1>;
FList2: TArray<TList2>;
public
procedure FillArray<T>(var List: TArray<T>; Source: TSource);
end;
procedure TSomeClass.FillArray<T>(var List: TArray<T>; Source: TSource);
begin
for i := 0 to Source.List1.Count - 1 do begin
SetLength(List, Length(List) + 1);
List[i] := T.Create(Source.List1[i].Key, Source.List1[i].Value);
end;
end;
This, or something like it should do what you want, afaict.

Related

Delphi TList<T> generics

Can someone explain to me if this is possible, or I'm completely missunderstanding this Delphi feature.
Let's say I have a class, I create a few of them, and then add them to an ObjectList. Normally I do it like this:
Type TMyClass = class(TObject)
stuff: string;
..
end;
Var things: TObjectList;
things := TObjectList.Create;
things.Add(TMyClass.Create);
// now I want to access stuff, so I need to typecast the class
TMyClass(things[0]).stuff..
So now my question, is it possible to declare the list in a way where I could just do like.. things[0].stuff and still have access to the the usual TObjectList features like .sort .indexof etc..? (without making a special class for this to simulate the objectlist)
You are using the TObjectList from System.Contnrs, which manages a list of pointers.
You want TObjectList from System.Generics.Collections. I know, using the same name can be a little confusing.
Type TMyClass = class(TObject)
stuff: string;
..
end;
Var things: TObjectList<TMyCLass>;
things := TObjectList<TMyCLass>.Create;
things.Add(TMyClass.Create);
things[0].stuff..

search a Generic list

I 'm trouble understanding in modifying the solution from GENERIC SEARCH
as my class is more complex and I need to create several different search functions
procedure TForm1.Button1Click(Sender: TObject);
var
activities: TList<TActivityCategory>;
search: TActivityCategory;
begin
activities := TObjectList<TActivityCategory>.Create(
TDelegatedComparer<TActivityCategory>.Create(
function(const Left, Right: TActivityCategory): Integer
begin
Result := CompareText(Left.Name, Right.Name);
end));
.....
Assume my TActivityCategory looks like
TActivityCategory = class
FirstName : String;
Secondname : String;
onemore .....
end;
How to implement a search for every String inside my activtity class ?
In your place I would write a subclass of TObjectList and add a custom Search method that would look like this:
TSearchableObjectList<T:class> = class(TObjectList<T>)
public
function Search(aFound: TPredicate<T>): T;
end;
The implementation for that method is
function TSearchableObjectList<T>.Search(aFound: TPredicate<T>): T;
var
item: T;
begin
for item in Self do
if aFound(item) then
Exit(item);
Result := nil;
end;
An example of this method is
var
myList: TSearchableObjectList<TActivitycategory>;
item: TActivitycategory;
searchKey: string;
begin
myList := TSearchableObjectList<TActivitycategory>.Create;
// Here you load your list
searchKey := 'WantedName';
// Let´s make it more interesting and perform a case insensitive search,
// by comparing with SameText() instead the equality operator
item := myList.Search(function(aItem : TActivitycategory): boolean begin
Result := SameText(aItem.FirstName, searchKey);
end);
// the rest of your code
end;
The TPredicate<T> type used above is declared in SysUtils, so be sure to add it to your uses clause.
I believe this is the closest we can get to lambda expressions in Delphi.
TList supports searching for items using either linear or binary search. With binary search, the algorithm assumes an ordering. That's not appropriate for your needs. Linear search seems to me to be what you need, and it's available through the Contains method.
The problem is that Contains assumes that you are searching for an entire instance of T. You want to pass a single string to Contains but it won't accept that. It wants a complete record, in your case.
You could provide a Comparer that only compares a single field. And then pass Contains a record with just that one field specified. But that's pretty ugly. Frankly the design of this class is very weak when it comes to searching and sorting. The fact that the comparer is a state variable rather than a parameter is a shocking lapse in my view.
The bottom line is that TList does not readily offer what you are looking for without resorting to ugliness. You should probably implement an old-fashion loop across the list to look for your match.
Note that I'm assuming you want to provide a single string and search for an entry that has a field matching the string. If in fact you do want to provide a complete record and match every field, then Contains does what you need, with suitable Comparer using a lexicographic ordering.

store array of TPoint inside TObjectList

I defined a Objectlist to store several Polygons as TFPolygon = array of TPoint inside this TObjectList; but with the add function of my objectlist I get an Access violation error
:
type
TFPolygon = array of TPoint;
TFPolygonList = class(TObjectList)
private
procedure SetPolygon(Index: Integer; Value: TFPolygon);
function GetPolygon(Index: Integer): TFPolygon;
public
procedure Add(p: TFPolygon);
property Items[index: Integer]: TFPolygon read GetPolygon write SetPolygon; default;
end;
implementation
procedure TFPolygonList.SetPolygon(Index: Integer; Value: TFPolygon);
begin
inherited Items[Index] := Pointer(Value);
end;
function TFPolygonList.GetPolygon(Index: Integer): TFPolygon;
begin
Result := TFPolygon(inherited Items[Index]);
end;
procedure TFPolygonList.Add(p: TFPolygon);
begin
inherited Add(Pointer(p));
end;
I can not understand the error inside this code sample ? Can I only store classes inside a TObjectList or is my approach to store arrays of TPoints also valid ?
Your approach is not valid. Dynamic arrays are managed types. Their lifetimes are managed by the compiler. For that to work you must not cast away the fact that they are managed types, which is exactly what you did.
You cast the dynamic array to Pointer. At that point you have taken a new reference to the dynamic array, but the compiler is not aware of it because a Pointer is not a managed type.
You've got a few options to solve your problem.
If you are on a modern Delphi then stop using TObjectList. Instead use the generic type safe containers in Generics.Collections. In your case TList<TFPolygon> is what you need. Because this is compile time type safe, all the lifetime of the managed types is taken care of.
If you are on an older Delphi, then you can wrap your dynamic array inside a class. Then add instances of those classes to your TObjectList. Make sure that your list is configured to own its objects. It's perfectly possible for you to do that wrapping purely in the implementation of TFPolygonList which will encapsulate things well.

How to Start Creating My Own Classes with Delphi?

I posted a question a few days ago, and the answers told me to create my own classes.
I'm an old-school programmer from the pre-OOP days my programming is well structured, efficient and organized, but lacks in any custom OOPing other than using Delphi and 3rd party objects.
I had looked at how Delphi's object oriented classes worked back when I started using Delphi 2, but they seemed foreign to my programming background. I understand how they were and are excellent for developers designing components and for visual controls on the user interface. But I never found the need to use them in the coding of my program itself.
So now I look again, 15 years later, at Delphi's classes and OOPing. If I take, for example, a structure that I have such as:
type
TPeopleIncluded = record
IndiPtr: pointer;
Relationship: string;
end;
var
PeopleIncluded: TList<TPeopleIncluded>;
Then an OOP advocator will probably tell me to make this a class. Logically, I would think this would be a class inherited from the generic TList. I would guess this would be done like this:
TPeopleIncluded<T: class> = class(TList<T>)
But that's where I get stuck, and don't have good instructions on how ot do the rest.
When I look at some class that Delphi has as an example in the Generics.Collections unit, I see:
TObjectList<T: class> = class(TList<T>)
private
FOwnsObjects: Boolean;
protected
procedure Notify(const Value: T; Action: TCollectionNotification); override;
public
constructor Create(AOwnsObjects: Boolean = True); overload;
constructor Create(const AComparer: IComparer<T>; AOwnsObjects: Boolean = True); overload;
constructor Create(Collection: TEnumerable<T>; AOwnsObjects: Boolean = True); overload;
property OwnsObjects: Boolean read FOwnsObjects write FOwnsObjects;
end;
and then their definitions of the constructors and procedures are:
{ TObjectList<T> }
constructor TObjectList<T>.Create(AOwnsObjects: Boolean);
begin
inherited;
FOwnsObjects := AOwnsObjects;
end;
constructor TObjectList<T>.Create(const AComparer: IComparer<T>; AOwnsObjects: Boolean);
begin
inherited Create(AComparer);
FOwnsObjects := AOwnsObjects;
end;
constructor TObjectList<T>.Create(Collection: TEnumerable<T>; AOwnsObjects: Boolean);
begin
inherited Create(Collection);
FOwnsObjects := AOwnsObjects;
end;
procedure TObjectList<T>.Notify(const Value: T; Action: TCollectionNotification);
begin
inherited;
if OwnsObjects and (Action = cnRemoved) then
Value.Free;
end;
Let me tell you that this "simple" class definition may be obvious to those of you who have used OOP in Delphi for years, but to me it only provides me with hundreds of unanswered questions on what do I use and how do I use it.
To me, this does not appear to be a science. It appears to be an art of how to best structure your information into objects.
So this question, and I hope it doesn't get closed because I really need help with this, is where or how do I get the best instruction on using Delphi to create classes - and how to do it the proper Delphi way.
To me, this does not appear to be a science. It appears to be an art
of how to best structure your information into objects.
Well... Yeah. There really aren't a lot of formal requirements. It's really just a set of tools to help you organize your ideas, and eliminate a lot of duplication along the way.
Then an OOP advocator will probably tell me to make this a class. Logically, I would think this would be a class inherited from the generic TList.
Actually, the whole point of generic containers is that you don't have to make a new container class for each type of object. Instead, you'd make a new content class and then create a TList<TWhatever>.
Think of a class instance as a pointers to a record.
Now: why use a class when you could use a pointer to a record? A couple reasons:
encapsulation: You can hide some aspects of the implementation with the private keyword so that other developers (including your future self) know not to depend on implementation details that may change or that just aren't important to understanding the concept.
polymorphism: You can avoid a lot of special dispatch logic by giving each of your records a set of pointers to functions. Then, rather than having a large case statement where you do different things for each type of object, you loop through your list and send each object the same message, then it follows the function pointer to decide what to do.
inheritance: As you start making records with pointers to functions and procedures, you find that you often have cases where you need a new function-dispatch record that's very much like one you already have, except you need to change one or two of the procedures. Subclassing is just a handy way to make that happen.
So in your other post, you indicated that your overall program looks like this:
procedure PrintIndiEntry(JumpID: string);
var PeopleIncluded : TList<...>;
begin
PeopleIncluded := result_of_some_loop;
DoSomeProcess(PeopleIncluded);
end;
It's not clear to me what Indi or JumpID mean, so I'm going to pretend that your company does skydiving weddings, and that Indi means "individual" and JumpID is a primary key in a database, indicating a flight where all those individuals are in the wedding party and scheduled to jump out of the same plane... And it's vitally important to know their Relationship to the happy couple so that you can give them the right color parachute.
Obviously, that isn't going to match your domain exactly, but since you're asking a general question here, the details don't really matter.
What the people in the other post were trying to tell you (my guess anyway) wasn't to replace your list with a class, but to replace the JumpID with one.
In other words, rather than passing JumpID to a procedure and using that to fetch the list of people from a database, you create a Jump class.
And if your JumpID actually indicates a jump as in goto, then you'd probably actually a bunch of classes that all subclass the same thing, and override the same method in different ways.
In fact, let's assume that you do some parties that aren't weddings, and in that case, you don't need the Relationships, but only a simple list of people:
type TPassenger = record
FirstName, LastName: string;
end;
type TJump = class
private
JumpID : string;
manifest : TList< TPassenger >;
public
constructor Init( JumpID: string );
function GetManifest( ) : TList< TPassenger >;
procedure PrintManifest( ); virtual;
end;
So now PrintManifest() does the job of your PrintIndyEntry(), but instead of calculating the list inline, it calls Self.GetManifest().
Now maybe your database doesn't change much, and your TJump instance is always short lived, so you decide to just populate Self.manifest in the constructor. In that case, GetManifest() just returns that list.
Or maybe your database changes frequently, or the TJump sticks around long enough that the database may change underneath it. In that case, GetManifest() rebuilds the list each time it's called... Or perhaps you add another private value indicating the last time you queried, and only update after the information expires.
The point is that PrintManifest doesn't have to care how GetManifest works, because you've hidden that information away.
Of course, in Delphi, you could have done the same thing with a unit, hiding a list of cached passenger lists in your implementation section.
But clasess bring a little more to the table, when it comes time to implement the wedding-party-specific features:
type TWeddingGuest = record
public
passenger : TPassenger;
Relationship : string;
end;
type TWeddingJump = class ( TJump )
private
procedure GetWeddingManifest( ) : TList< TWeddingGuest >;
procedure PrintManifest( ); override;
end;
So here, the TWeddingJump inherits the Init and GetManifest from the TJump, but it also adds a GetWeddingManifest( );, and it's going to override the behavior of PrintManifest() with some custom implementation. (You know it's doing this because of the override marker here, which corresponds to the virtual marker in TJump.
But now, suppose that PrintManifest is actually a rather complicated procedure, and you don't want to duplicate all that code when all you want to do is add one column in the header, and another column in the body listing the relationship field. You can do that like so:
type TJump = class
// ... same as earlier, but add:
procedure PrintManfestHeader(); virtual;
procedure PrintManfiestRow(passenger:TPassenger); virtual;
end;
type TWeddingJump = class (TJump)
// ... same as earlier, but:
// * remove the PrintManifest override
// * add:
procedure PrintManfestHeader(); override;
procedure PrintManfiestRow(passenger:TPassenger); override;
end;
Now, you want to do this:
procedure TJump.PrintManifest( )
var passenger: TPassenger;
begin;
// ...
Self.PrintManifestHeader();
for guest in Self.GetManifest() do begin
Self.PrintManifestRow();
end;
// ...
end;
But you can't, yet, because GetManifest() returns TList< TPassenger >; and for TWeddingJump, you need it to return TList< TWeddingGuest >.
Well, how can you handle that?
In your original code, you have this:
IndiPtr: pointer
Pointer to what? My guess is that, just like this example, you have different types of individual, and you need them to do different things, so you just use a generic pointer, and let it point to different kinds of records, and hope you cast it to the right thing later. But classes give you several better ways to solve this problem:
You could make TPassenger a class and add a GetRelationship() method. This would eliminate the need for TWeddingGuest, but it means that GetRelationship method is always around, even when you're not talking about weddings.
You could add a GetRelationship(guest:TPassenger) in the TWeddingGuest class, and just call that inside TWeddingGuest.PrintManifestRow().
But suppose you have to query a database to populate that information. With the two methods above, you're issuing a new query for each passenger, and that might bog down your database. You really want to fetch everything in one pass, in GetManifest().
So, instead, you apply inheritance again:
type TPassenger = class
public
firstname, lastname: string;
end;
type TWeddingGuest = class (TPassenger)
public
relationship: string;
end;
Because GetManifest() returns a list of passengers, and all wedding guests are passengers, you can now do this:
type TWeddingJump = class (TJump)
// ... same as before, but:
// replace: procedure GetWeddingManfiest...
// with:
procedure GetManifest( ) : TList<TPassenger>; override;
// (remember to add the corresponding 'virtual' in TJump)
end;
And now, you fill in the details for TWeddingJump.PrintManifestRow, and the same version of PrintManifest works for both TJump and TWeddingJump.
There's still one problem: we declared PrintManifestRow(passenger:TPassenger) but we're actually passing in a TWeddingGuest. This is legal, because TWeddingGuest is a subclass of TPassenger... But we need to get at the .relationship field, and TPassenger doesn't have that field.
How can the compiler trust that inside a TWeddingJump, you're always going to pass in a TWeddingGuest rather than just an ordinary TPassenger? You have to assure it that the relationship field is actually there.
You can't just declare it as TWeddingJupmp.(passenger:TWeddingGuest) because by subclassing, you're basically promising to do all the things the parent class can do, and the parent class can handle any TPassenger.
So you could go back to checking the type by hand and casting it, just like an untyped pointer, but again, there are better ways to handle this:
Polymorphism approach: move the PrintManifestRow() method to the TPassenger class (removing the passenger:TPassenger parameter, as this is now the implicit parameter Self), override that method in TWeddingGuest, and then just have TJump.PrintManifest call passenger.PrintManifestRow().
Generic class approach: make TJump itself a generic class (type TJump<T:TPassenger> = class), and instead of having GetManifest() return a TList<TPassenger>, you have it return TList<T>. Likewise, PrintManifestRow(passenger:TPassenger) becomes PrintManifestRow(passenger:T);. Now you can say: TWeddingJump = class(TJump<TWeddingGuest>) and now you're free to declare the overridden version as PrintManifestRow(passenger:TWeddingGuest).
Anyway, that's way more than I expected to write about all this. I hope it helped. :)

What are good uses for class helpers?

Delphi (and probably a lot of other languages) has class helpers. These provide a way to add extra methods to an existing class. Without making a subclass.
So, what are good uses for class helpers?
I'm using them:
To insert enumerators into VCL classes that don't implement them.
To enhance VCL classes.
To add methods to the TStrings class so I can use the same methods in my derived lists and in TStringList.
TGpStringListHelper = class helper for TStringList
public
function Last: string;
function Contains(const s: string): boolean;
function FetchObject(const s: string): TObject;
procedure Sort;
procedure Remove(const s: string);
end; { TGpStringListHelper }
To simplify access to record fields and remove casting.
At first I was kind of sceptic about class helpers. But then I read an interesting blog entry and now I'm convinced that they are indeed useful.
For example, if you want extra functionality for an existing instance class and for some reason you are not able to change the existing source. You can create a class helper to add this functionality.
Example:
type
TStringsHelper = class helper for TStrings
public
function IsEmpty: Boolean;
end;
function TStringsHelper.IsEmpty: Boolean;
begin
Result := Count = 0;
end;
Every time, we now use an instance of (a subclass of) TStrings, and TStringsHelper is within the scope. We have access to the method IsEmpty.
Example:
procedure TForm1.Button1Click(Sender: TObject);
begin
if Memo1.Lines.IsEmpty then
Button1.Caption := 'Empty'
else
Button1.Caption := 'Filled';
end;
Notes:
Class helpers can be stored in a separate unit, so you can add your own nifty class helpers. Be sure to give these units a easy to remember name like ClassesHelpers for helpers for the Classes unit.
There are also record helpers.
If there are multiple class helpers within scope, expect some problems, only one helper can be used.
This sounds very much like extension methods in C#3 (and VB9). The best use I've seen for them is the extensions to IEnumerable<T> (and IQueryable<T>) which lets LINQ work against arbitrary sequences:
var query = someOriginalSequence.Where(person => person.Age > 18)
.OrderBy(person => person.Name)
.Select(person => person.Job);
(or whatever, of course). All of this is doable because extension methods allow you to effectively chain together calls to static methods which take the same type as they return.
They're very useful for plug-ins. For example, let's say your project defines a certain data structure and it's saved to disc in a certain way. But then some other program does something very similar, but the data file's different. But you don't want to bloat your EXE with a bunch of import code for a feature that a lot of your users won't need to use. You can use a plugin framework and put importers into a plugin that would work like this:
type
TCompetitionToMyClass = class helper for TMyClass
public
constructor Convert(base: TCompetition);
end;
And then define the converter. One caveat: a class helper is not a class friend. This technique will only work if it's possible to completely setup a new TMyClass object through its public methods and properties. But if you can, it works really well.
I would not recommend to use them, since I read this comment:
"The biggest problem with class
helpers, from the p.o.v of using them
in your own applications, is the fact
that only ONE class helper for a given
class may be in scope at any time."
... "That is, if you have two helpers
in scope, only ONE will be recognised
by the compiler. You won't get any
warnings or even hints about any other
helpers that may be hidden."
http://davidglassborow.blogspot.com/2006/05/class-helpers-good-or-bad.html
The first time I remember experiencing what you're calling "class helpers" was while learning Objective C. Cocoa (Apple's Objective C framework) uses what are called "Categories."
A category allows you to extend an existing class by adding you own methods without subclassing. In fact Cocoa encourages you to avoid subclassing when possible. Often it makes sense to subclass, but often it can be avoided using categories.
A good example of the use of a category in Cocoa is what's called "Key Value Code (KVC)" and "Key Value Observing (KVO)."
This system is implemented using two categories (NSKeyValueCoding and NSKeyValueObserving). These categories define and implement methods that can be added to any class you want. For example Cocoa adds "conformance" to KVC/KVO by using these categories to add methods to NSArray such as:
- (id)valueForKey:(NSString *)key
NSArray class does not have either a declaration nor an implementation of this method. However, through use of the category. You can call that method on any NSArray class. You are not required to subclass NSArray to gain KVC/KVO conformance.
NSArray *myArray = [NSArray array]; // Make a new empty array
id myValue = [myArray valueForKey:#"name"]; // Call a method defined in the category
Using this technique makes it easy to add KVC/KVO support to your own classes. Java interfaces allow you to add method declarations, but categories allow you to also add the actual implementations to existing classes.
As GameCat shows, TStrings is a good candidate to avoid some typing:
type
TMyObject = class
public
procedure DoSomething;
end;
TMyObjectStringsHelper = class helper for TStrings
private
function GetMyObject(const Name: string): TMyObject;
procedure SetMyObject(const Name: string; const Value: TMyObject);
public
property MyObject[const Name: string]: TMyObject read GetMyObject write SetMyObject; default;
end;
function TMyObjectStringsHelper.GetMyObject(const Name: string): TMyObject;
var
idx: Integer;
begin
idx := IndexOf(Name);
if idx < 0 then
result := nil
else
result := Objects[idx] as TMyObject;
end;
procedure TMyObjectStringsHelper.SetMyObject(const Name: string; const Value:
TMyObject);
var
idx: Integer;
begin
idx := IndexOf(Name);
if idx < 0 then
AddObject(Name, Value)
else
Objects[idx] := Value;
end;
var
lst: TStrings;
begin
...
lst['MyName'] := TMyObject.Create;
...
lst['MyName'].DoSomething;
...
end;
Did you ever need to access multi line strings in the registry?
type
TRegistryHelper = class helper for TRegistry
public
function ReadStrings(const ValueName: string): TStringDynArray;
end;
function TRegistryHelper.ReadStrings(const ValueName: string): TStringDynArray;
var
DataType: DWord;
DataSize: DWord;
Buf: PChar;
P: PChar;
Len: Integer;
I: Integer;
begin
result := nil;
if RegQueryValueEx(CurrentKey, PChar(ValueName), nil, #DataType, nil, #DataSize) = ERROR_SUCCESS then begin
if DataType = REG_MULTI_SZ then begin
GetMem(Buf, DataSize + 2);
try
if RegQueryValueEx(CurrentKey, PChar(ValueName), nil, #DataType, PByte(Buf), #DataSize) = ERROR_SUCCESS then begin
for I := 0 to 1 do begin
if Buf[DataSize - 2] <> #0 then begin
Buf[DataSize] := #0;
Inc(DataSize);
end;
end;
Len := 0;
for I := 0 to DataSize - 1 do
if Buf[I] = #0 then
Inc(Len);
Dec(Len);
if Len > 0 then begin
SetLength(result, Len);
P := Buf;
for I := 0 to Len - 1 do begin
result[I] := StrPas(P);
Inc(P, Length(P) + 1);
end;
end;
end;
finally
FreeMem(Buf, DataSize);
end;
end;
end;
end;
I've seen them used for making available class methods consistent across classes: Adding Open/Close and Show/Hide to all classes of a given "type" rather than only Active and Visible properties.
If Dephi supported extension methods, one use i want is:
TGuidHelper = class
public
class function IsEmpty(this Value: TGUID): Boolean;
end;
class function TGuidHelper(this Value: TGUID): Boolean;
begin
Result := (Value = TGuid.Empty);
end;
So i can call if customerGuid.IsEmpty then ....
Another good example is to be able to read values from an XML document (or JSON if you're into that sort of thing) with the IDataRecord paradigm (which i love):
orderGuid := xmlDocument.GetGuid('/Order/OrderID');
Which is much better than:
var
node: IXMLDOMNode;
node := xmlDocument.selectSingleNode('/Order/OrderID');
if Assigned(node) then
orderID := StrToGuid(node.Text) //throw convert error on empty or invalid
else
orderID := TGuid.Empty; // "DBNull" becomes the null guid
Other languages have properly designed class helpers.
Delphi has class helpers that were introduced solely to help the Borland engineers with a compatibility problem between Delphi and Delphi.net.
They were never intended to be used in "user" code and have not been improved since. They can be helpful if developing frameworks (for private use within the framework, as with the original .NET compatibility solution); it is dangerously misguided to equate Delphi class helpers with those in other languages or to draw on examples from those other languages in an effort to identify use cases for those in Delphi.
To this day, the current Delphi documentation has this to say about class and record helpers:
they should not be viewed as a design tool to be used when developing new code
ref: https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Class_and_Record_Helpers_(Delphi)
So the answer to the question "what are the good uses for class helpers" in Delphi specifically is quite simple:
There is only one safe use: For context-specific extensions of utility and interest only in the single codebase that implements and consumes the helper (detailed example here: https://www.deltics.co.nz/blog/posts/683).
The example is a framework for restful API where extensions to a class of interest only to client-side code are provided by "Client Helper" extensions, explicitly imported from client-specific units rather than (over)loading client-concerns into an original class with both server and client context.
Other than that: Do not use them at all (either implementing your own or consuming those provided by others) unless you are prepared to deal with the consequences:
Primarily: Only one helper can be in scope at any time
Secondarily: There is no way to qualify helper referenced
Because of the primary problem:
Adding a unit to (or even just changing the order of) the units in a uses clause may inadvertently "hide" a helper needed in your code (you may not even know where from)
A helper added to a unit already in your uses list could hide some other helper previously "imported" and used from another
And thanks to the secondary problem, if you are unable to re-order the uses list to make a desired helper "visible" or you need 2 unrelated helpers (unaware of each other and so unable to "extent" one another), then there is no way to use it!
Worth emphasising here is that the ability of Delphi class helpers to break other people's code is an almost uniquely bad characteristic. Many language features in many languages can be abused to break your own code; not many enable you to break someone else's!
More details in various posts here: https://www.deltics.co.nz/blog/?s=class+helpers
Particularly this one: https://www.deltics.co.nz/blog/posts/273/

Resources