What are good uses for class helpers? - delphi

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/

Related

Are Delphi record constructors really needed?

SITUATION
I am studying "More Coding in Delphi" by Nick Hodges, and he is using a TFraction record to explain operator overloading. I have written by myself this record:
type
TFraction = record
strict private
aNumerator: integer;
aDenominator: integer;
function GCD(a, b: integer): integer;
public
constructor Create(aNumerator: integer; aDenominator: integer);
procedure Reduce;
class operator Add(fraction1, fraction2: TFraction): TFraction;
class operator Subtract(fraction1, fraction2: TFraction): TFraction;
//... implicit, explicit, multiply...
property Numerator: integer read aNumerator;
property Denominator: integer read aDenominator;
end;
Of course, I had to create a constructor because in Q (rationals) I must have a denominator that is not equal to zero.
constructor TFraction.Create(aNumerator, aDenominator: integer);
begin
if (aDenominator = 0) then
begin
raise Exception.Create('Denominator cannot be zero in rationals!');
end;
if ( (aNumerator < 0) or (aDenominator < 0) ) then
begin
Self.aNumerator := -aNumerator;
Self.aDenominator := -aDenominator;
end
else
begin
Self.aNumerator := aNumerator;
Self.aDenominator := aDenominator;
end;
end;
PROBLEM
Since the operator overloads return a TFraction, I am going to define an operation like this:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
var
tmp: TFraction;
begin
//simple algorithm of the sum
tmp := TFraction.Create(fraction1.Numerator*fraction2.Denominator+fraction1.Denominator*fraction2.Numerator, fraction1.Denominator*fraction2.Denominator);
tmp.Reduce;
//return the result
Result := tmp;
end;
As you can see here, I am creating a tmp that is returned from the function.
When I read Marco Cantu's book, he used another approach:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
Result.aNumerator := (fraction1.Numerator*fraction2.Denominator+fraction1.Denominator*fraction2.Numerator);
Result.aDenominator := fraction1.Denominator*fraction2.Denominator;
end;
I have made some tests, and I see that both give me the correct result, but there is something that I cannot understand. In the first approach, I am declaring tmp and then I call the constructor so I can return a TFraction. In the second approach, I am instead not creating anything because records have an automatic constructor. The documentation, in fact, says that:
Records are constructed automatically, using a default no-argument
constructor, but classes must be explicitly constructed. Because
records have a default no-argument constructor, any user-defined
record constructor must have one or more parameters.
Here I have a user-defined record constructor. So:
Is the constructor call on tmp of the first approach not needed? If I want to call Reduce (which is a procedure), I need to create a variable. Is the Result just returning a copy of tmp without creating anything?
In the second approach, are Result.aNumerator and Result.aDenominator the parameters of the automatic created constructor?
A record constructor isn't anything magical. It's just an instance method like any other. You write:
tmp := TFraction.Create(...);
But you may equally well write it like this:
tmp.Create(...);
I personally find neither to be especially useful because I am used to constructor calling semantics for classes which allocate and default initialise memory, and then call the constructor method.
And especially the second variant grates with me because that looks like the classic mistake that novice Delphi programmers make when starting out and trying to create an instance of a class. That code would be no good if TFraction were a class, but for a record it is fine.
Were it me I would get rid of the record constructor and instead use a static class function that returned a newly minted instance of your record type. My convention is to name such things New. But these are matters of personal preference.
If you did that it would be declared like this:
class function New(aNumerator, aDenominator: Integer): TFraction; static;
It would be implemented like this:
class function TFraction.New(aNumerator, aDenominator: Integer): TFraction;
begin
Result.aNumerator := ...;
Result.aDenominator := ...;
end;
You would then call it like this:
frac := TFraction.New(num, denom);
But as I said, that's a matter of preference. If you like record constructors, feel free to stick with them.
You ask whether or not you can skip the constructor. In terms of allocation of the record, yes you can skip it. In terms of running the code in the constructor, only you can determine that. Do you want that code to execute or not?
If you wish that code to be executed, but don't want to use a temporary variable, then you can write the code like this:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
Result.Create(
fraction1.Numerator*fraction2.Denominator + fraction1.Denominator*fraction2.Numerator,
fraction1.Denominator*fraction2.Denominator
);
Result.Reduce;
end;
Or if you preferred a static class function it would be:
class operator TFraction.Add(fraction1, fraction2: TFraction): TFraction;
begin
Result := TFraction.New(
fraction1.Numerator*fraction2.Denominator + fraction1.Denominator*fraction2.Numerator,
fraction1.Denominator*fraction2.Denominator
);
Result.Reduce;
end;

How do I assign a pre-existing function to a TComparison<T>?

program Project55;
{$APPTYPE CONSOLE}
uses
System.Generics.Defaults;
type
TestRec<T> = record
Compare: TComparison<T>;
CompareI: IComparer<T>;
end;
var
TRI: TestRec<Integer>;
begin
TRI.CompareI:= TComparer<Integer>.Default;
TRI.Compare:= TRI.CompareI.Compare; //E2035 Not enough actual parameters
TRI.Compare:= #TRI.CompareI.Compare; //E2035 Not enough actual parameters
end.
I know I can assign the function body as an anonymous function, but why can't I assign an existing function?
Of course the following works, but that's just silly:
TRI.Compare:= function(const L,R: integer): Integer
begin
Result:= TRI.CompareI.Compare(L,R);
end;
PS. I'm using Delphi XE7, but I doubt the version matters.
Knowing that IComparer<T> is an interface with just one method that has the same signature as TComparison<T> and that anonymous methods are just interfaces with one method you can do the following.
IComparer<Integer>(TRI.Compare) := TRI.CompareI;
I am using that trick in Spring4D to avoid creating a wrapper object around a TComparison<T> to be passed as IComparer<T> because they are binary compatible.
Your attempts to perform this assignment fail because an interface method cannot be with assigned to a method reference variable. The language simply does not permit that. The types are not assignment compatible. Valid assignment sources are anonymous methods, methods of classes (instance or class) and unit scope procedures.
The tricks that can be seen in other answers all depend on in depth knowledge of the implementation details. Which means that they are subject to change. But in terms of the language, what you are attempting is not permitted.
Anonymous methods are not exactly method pointers. They are implemented as an interface with a single method "Invoke".
It is possible to extract a method pointer from an anonymous method, but as far as I know it relies on the current implementation details of anonymous method and could be subject to changes in future version of delphi. In other words, I would advise against it. This was taken verbatim from Barry Kelly's post here. (Which covers the topic more thoroughly than I do here)
procedure MethRefToMethPtr(const MethRef; var MethPtr);
type
TVtable = array[0..3] of Pointer;
PVtable = ^TVtable;
PPVtable = ^PVtable;
begin
// 3 is offset of Invoke, after QI, AddRef, Release
TMethod(MethPtr).Code := PPVtable(MethRef)^^[3];
TMethod(MethPtr).Data := Pointer(MethRef);
end;
Based on your example, I'd propose this as an alternative
type
TestRec<T> = record
CompareI: IComparer<T>;
function Compare(const L, R : T) : Integer;
end;
[...]
function TestRec<T>.Compare(const L, R : T) : Integer;
begin
Result := CompareI.Compare(L,R);
end;
But then, it may/may not apply to your current situation.

Can I use generics to do the same operation on similar types of controls?

I am using Delphi 2010 and I have a unit where over the years I have added my own procedures and functions that can be used with any project I make, such as:
function ListBoxIsSelected(ListBox: TListBox): Boolean;
begin
Result:= ListBox.ItemIndex <> -1;
end;
The above uses TListBox as a parameter, so whenever the above function is used I must supply a listbox that is of TListBox class.
Now suppose I have some other component libraries that could work with the same function, For example the Jedi component classes.
How could I use the above function, when the Jedi listbox is TJvListBox class and my function is looking for TListBox class? Although both components are practically the same, the class names are different. If I provided the same function specifically for the TJvListBox it would likely work because they are both "listboxes":
function ListBoxIsSelected(ListBox: TJvListBox): Boolean;
begin
Result:= ListBox.ItemIndex <> -1;
end;
Now, I have whole load of procedures and functions written in the same kind of way where I need to pass a component as a parameter. Having to rewrite them again just to work with a different component class is not feasible!
How can I write this with generics?
You can't write that with generics, unless your target classes all descend from the same base class of course. (But then you wouldn't need generics for it.)
If you really want something that can check if the ItemIndex property on any object <> -1, though, you can do that with a different Delphi 2010 feature: extended RTTI.
uses
SysUtils, RTTI;
function IsSelected(item: TObject): boolean;
var
context: TRttiContext;
cls: TRttiType;
prop: TRttiProperty;
ItemIndex: integer;
begin
if item = nil then
raise Exception.Create('Item = nil');
context := TRttiContext.Create;
cls := context.GetType(item.ClassType);
prop := cls.GetProperty('ItemIndex');
if prop = nil then
raise Exception.Create('Item does not contain an ItemIndex property.');
ItemIndex := prop.GetValue(item).AsInteger;
result := ItemIndex <> -1;
end;
Careful, though. There's no compile-time type checking here, and this process is significantly slower than your original routine. You probably won't notice it, but if you call something like this in a tight loop, it will slow it down.
I don't understand how I can write this with Generics?
You can’t – not unless your component implements a common interface or inherits from a common base class with the standard ListBox, and that interface / base class offers the ItemIndex property.
In fact, this use-case isn’t such a great example of generics because using an interface or base class in the declaration would work just as well.
In this case, you can write two overloaded functions, one expecting TJvListBox and the other expecting TListBox.
In more complex cases this approach may not apply so well, but I think your case is simple enough for this solution.
I cannot look it up right now (on holiday, no Delphi), but don't TJvListBox and TListBox descend from a common ancestor (my guess would be: TCustomListBox)? In that case something like this should work:
interface
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
implementation
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
begin
Result := _ListBox.ItemIndex <> -1;
end;
Just in case ItemIndex (as I said: I cannot check right now) is protected in TCustomListBox, you can just use a typecast hack:
type
TListBoxHack = class(TCustomListBox)
end;
function TListBox_IsItemSelected(_ListBox: TCustomListBox): boolean;
begin
Result := TListBoxHack(_ListBox).ItemIndex <> -1;
end;
(I just thought I should mention this since the original question has already been answered: Using Generics does not help here.)

Delphi: how to use TObjectList<T>?

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.

RTTI: Can I Get a Type by Name?

Given a text string containing a type name, is there some way to get the appropriate type itself?
I'm looking to do something like this:
type
TSomeType<T> = class
// yadda yadda
end;
procedure DoSomething;
var
obj : TObject;
begin
o := TSomeType<GetTypeByName('integer')>.Create;
// do stuff with obj
end;
I've looked at several RTTI explanations online and looked through the Delphi units and don't see what I'm looking for. Is this possible?
No, generics are entirely compiletime.
The new RTTI unit in Delphi 2010 has a way of retrieving types declared in the interface section of units. For any given type, represented by a TRttiType instance, the TRttiType.QualifiedName property returns a name that can be used with TRttiContext.FindType later to retrieve the type. The qualified name is the full unit name (including namespaces, if they exist), followed by a '.', followed by the full type name (including outer types if it nested).
So, you could retrieve a representation of the Integer type (in the form of a TRttiType) with context.FindType('System.Integer').
But this mechanism can't be used to retrieve instantiations of generic types that weren't instantiated at compile time; instantiation at runtime requires runtime code generation.
You can always register your types into some sort of registry (managed by a string list or dictionary) and create a factory function to then return the appropriate object. Unfortunately you would have to know in advance what types you were going to need. Something similar to the Delphi functions RegisterClass and FindClass (in the classes unit). My thinking is to put the generic template type into the list directly.
An example of possible usage:
RegisterCustomType('Integer',TSomeType<Integer>);
RegisterCustomType('String',TSomeType<String>);
if FindCustomType('Integer') <> nil then
O := FindCustomType('Integer').Create;
EDIT: Here is a specific simple implementation using a tDictionary from Generics.Collections to handle the registry storage...I'll leave extracting this into useful methods as a simple exercise for the reader.
var
o : TObject;
begin
TypeDict := TDictionary<String,TClass>.Create;
TypeDict.Add('integer',TList<integer>);
if TypeDict.ContainsKey('integer') then
o := TypeDict.Items['integer'].Create;
if Assigned(o) then
ShowMessage(o.ClassName);
end;
Another EDIT: I was giving this some thought last night, and discovered another technique that you can merge into this concept. Interfaces. Here is a quick do nothing example, but can easily be extended:
TYPE
ITest = interface
['{0DD03794-6713-47A0-BBE5-58F4719F494E}']
end;
TIntfList<t> = class(TList<T>,ITest)
public
function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
procedure TForm1.Button7Click(Sender: TObject);
var
o : TObject;
fTestIntf : ITest;
begin
TypeDict := TDictionary<String,TClass>.Create;
TypeDict.Add('integer',TIntfList<integer>);
if TypeDict.ContainsKey('integer') then
o := TypeDict.Items['integer'].Create;
if Assigned(o) and Supports(o,ITest,fTestIntf) then
ShowMessage(o.ClassName);
end;
of course you would have to implement the QueryInterface, _AddRef and _Release methods and extend the interface to do something more useful.
If you forget generics and basic types, the "RegisterClass" function would be helpful. But it doesn't work for generics or basic types.

Resources