Delphi Generic Template classes Usage Compilation Error - delphi

I am new to Delphi Generic Classes. I don't get it of how to use the generic classes in the implementation code.
Here is the code:
Type TDataElement = class(TObject)
protected
Procedure SetName(sNewValue:String); virtual;
private
m_sName:String;
published
property sName:String read m_sName write SetName;
end;
Type TDataArray<T : TDataElement> = class(TObject)
public
function Find(dtElement:T):integer;
Procedure Add(dtElement:T);
private
m_vContainer : array of T;
protected
Function GetData(Index:integer):T; virtual;
Procedure SetData(Index:integer; NewValue:T); virtual;
public
property vData[Index: Integer]: T read GetData write SetData;
end;
implementation
function TDataArray<T>.Find(dtElement:T):integer;
var i:integer;
begin
Result:=-1;
for i := 0 to high(m_vContainer) do
if (m_vContainer[i] <> NIL)and(m_vContainer[i] = dtElement) then
begin
Result:=i;
exit;
end;
end;
.....
When I try to create instances of the Generic Classes like in the following code:
Method1)
var z:TDataArray<TDataElement>;
z:=TDataArray<TDataElement>.Create();
I get the following error:
E2010 Incompatible types: 'TDataElement' and 'class of TDataElement'
If I do this 2nd method I get another strange error:
Method 2)
type TDataElementClass = class of TDataElement;
var z:TDataArray<TDataElementClass>;
F2084 Internal Error : I8230
What I am doing wrong?
Entire source code in one file
System.SysUtils,Classes,
dtArray_unit in 'D:\VisionBot\Software\VisionBot\GUI\Units\dtArray_unit.pas';
Type TDataElement = class(TObject)
protected
Procedure SetName(sNewValue:String); virtual;
private
m_sName:String;
published
property sName:String read m_sName write SetName;
end;
Type TDataArray<T : TDataElement> = class(TObject)
public
function Find(dtElement:T):integer; overload;
Procedure Add(dtElement:T);
private
m_vContainer : array of T;
protected
Function GetData(Index:integer):T; virtual;
Procedure SetData(Index:integer; NewValue:T); virtual;
public
property vData[Index: Integer]: T read GetData write SetData;
end;
type
TDerivedDataElement = class(TDataElement)
end;
var
z2: TDataArray<TDerivedDataElement>;
//------------------------------------------------------------------------------
Procedure TDataElement.SetName(sNewValue:String);
begin
self.m_sName:=sNewValue;
end;
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
function TDataArray<T>.Find(dtElement:T):integer;
var i:integer;
begin
Result:=-1;
for i := 0 to high(m_vContainer) do
if (m_vContainer[i] <> NIL)and(m_vContainer[i] = dtElement) then
begin
Result:=i;
exit;
end;
end;
//------------------------------------------------------------------------------
Function TDataArray<T>.GetData(Index:integer):T;
begin
Result:=NIL;
if Index < 0 then exit else
if Index > high(Index) then exit else
Result:=self.m_vContainer[Index];
end;
//------------------------------------------------------------------------------
Procedure TDataArray<T>.Add(dtElement:T);
begin
SetLength(self.m_vContainer,Length(m_vContainer)+1);
m_vContainer[High(m_vContainer)]:=T;
end;
//------------------------------------------------------------------------------
Procedure TDataArray<T>.SetData(Index:integer; NewValue:T);
begin
if Index < 0 then exit else
if Index > high(Index) then exit else
self.m_vContainer[Index]:=T;
end;
//------------------------------------------------------------------------------
begin
try
z2:= TDataArray<TDerivedDataElement>.Create();
readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

var
z: TDataArray<TDataElementClass>;
The problem is that TDataElementClass is not a class derived from TDataElement.
The following would be valid:
var
z: TDataArray<TDataElement>;
Or this:
type
TDerivedDataElement = class(TDataElement)
end;
var
z: TDataArray<TDerivedDataElement>;
In your code you have
type
TDataElementClass = class of TDataElement;
Now, TDataElementClass is a metaclass.
A variable of type TDataElement can hold an instance of the type TDataElement, or an instance of any class derived from TDataElement.
A variable of type TDataElementClass can hold a type, which must be TDataElement, or any class derived from TDataElement.
You claim in the question that using TDataArray<TDataElement> leads to a compiler error, but that is not true. Consider this compiling program:
type
TDataElement = class
end;
type
TDataArray<T: TDataElement> = class
public
function Find(dtElement: T): Integer;
private
m_vContainer: array of T;
end;
function TDataArray<T>.Find(dtElement: T): Integer;
begin
for Result := 0 to high(m_vContainer) do
if (m_vContainer[Result] <> nil) and (m_vContainer[Result] = dtElement) then
exit;
Result := -1;
end;
var
arr: TDataArray<TDataElement>;
begin
arr := TDataArray<TDataElement>.Create;
end.
In your edit you show this code:
Procedure TDataArray<T>.Add(dtElement:T);
begin
SetLength(self.m_vContainer,Length(m_vContainer)+1);
m_vContainer[High(m_vContainer)]:=T;
end;
The erroneous line is here:
m_vContainer[High(m_vContainer)]:=T;
This fails because T is a type rather than an instance. I think you mean:
m_vContainer[High(m_vContainer)]:=dtElement;

Related

"Abstract Error" shows up when calling methods from child classes

I want to instantiate classes based on a parameter. Both classes are derived from TSample so I define my code as:
var T: TSample;
then I do
T := TMySample.Create;
or
T := TYourSample.Create;
and calling T.Hello gives an "Abstract Error".
type TSample = class
public
procedure Hello; virtual; abstract;
end;
TMySample = class(TSample)
public
procedure Hello;
end;
TYourSample = class(TSample)
public
procedure Hello;
end;
...
procedure TForm1.Button1Click(Sender: TObject);
var T: TSample;
a: Integer;
begin
if a = 1 then T := TMySample.Create
else T := TYourSample.Create;
T.Hello; //Abstract Error here
T.Free;
end;
procedure TMySample.Hello;
begin
showmessage('My');
end;
procedure TYourSample.Hello;
begin
showmessage('Your');
end;
You forgot to declare the overridden methods as, well, overridden:
TMySample = class(TSample)
public
procedure Hello; override; // <--
end;
TYourSample = class(TSample)
public
procedure Hello; override; // <--
end;
Actually, the compiler warned you about this, but you didn't listen :)
[dcc32 Warning] Unit1.pas(25): W1010 Method 'Hello' hides virtual method of base type 'TSample'
[dcc32 Warning] Unit1.pas(30): W1010 Method 'Hello' hides virtual method of base type 'TSample'
Also, you probably already know this, but there are two issues with your sample code:
Since local variables of non-managed types are not initialized, the value of a is undefined.
You don't protect the TSample object, so you might leak resources. (In fact, in this case, you will due to the exception!)
Fixed:
a := 123;
if a = 1 then
T := TMySample.Create
else
T := TYourSample.Create;
try
T.Hello; //Abstract Error here
finally
T.Free;
end;

How to save pointers to interface methods?

I have next code. How to store pointers to functions Voice declared by the interface in an array?
If the abstract class TAnimal is used instead of the IVoice interface, then the pointers to the Voice function are stored in the array successfully!
PS. Delphi 10.3 Rio
type
IVoice = interface
function Voice: string;
end;
TAnimal = class abstract (TInterfacedObject)
strict private
FName: string;
public
property Name: string read FName write FName;
end;
TDog = class(TAnimal, IVoice)
protected
function Voice: string;
end;
TCat = class(TAnimal, IVoice)
protected
function Voice: string;
end;
{ TDog }
function TDog.Voice: string;
begin
Result:= 'Arf-Arf!';
end;
{ TCat }
function TCat.Voice: string;
begin
Result:= 'Meow-Meow!';
end;
var
voices: TArray<IVoice>;
funcs: TArray<TFunc<string>>;
I: Integer;
begin
voices:= [TDog.Create, TCat.Create, TDog.Create];
SetLength(funcs, Length(voices));
for I := 0 to High(voices) do
funcs[i]:= voices[i].Voice; //<--- don't compile
for I := 0 to High(funcs) do
Writeln(funcs[i]());
Readln;
end.
I expect the output
Arf-Arf!
Meow-Meow!
Arf-Arf!
but this code don't compile with error:
E2010 Incompatible types: 'System.SysUtils.TFunc<System.string>' and 'string'
You have to manually wrap the call to the interface method in an anonymous method. Like this:
funcs[i]:=
function: string
begin
Result := voices[i].Voice;
end;

How to define a parameter of type generic list with constructor constraint?

I want to define three base classes, TMyBaseClass that keeps data, TMyBaseClassList that holds a list of instances of TMyBaseClass, and TMyBaseClassReader that scrolls through a dataset and fills a TMyBaseClassList object. This is my code:
TMyBaseClass = class
public
// properties
constructor Create;
end;
TMyBaseClassList<T: TMyBaseClass, constructor> = class(TObjectList<TMyBaseClass>)
public
function AddNew: T;
end;
TMyBaseClassReader<T: TMyBaseClass> = class
public
class procedure ReadProperties(const DataSet: TCustomADODataSet;
const Item: T); virtual; abstract;
class procedure ReadDataSet(const DataSet: TCustomADODataSet;
const List: TMyBaseClassList<T>);// <- E2513
end;
...
constructor TMyBaseClass.Create;
begin
inherited;
end;
function TMyBaseClassList<T>.AddNew: T;
begin
Result := T.Create;
Add(Result);
end;
class procedure TMyBaseClassReader<T>.ReadDataSet;
var
NewItem: T;
begin
while not DataSet.Eof do
begin
NewItem := List.AddNew;
ReadProperties(DataSet, NewItem);
DataSet.Next;
end;
end;
Then I want to derive child classes and only implement ReadProperties method. But I'm getting an E2513 error:
E2513 Type parameter 'T' must have one public parameterless constructor named Create
What is the problem and how can I fix it?
The error means that the compiler cannot be sure that T meets the requirements. Declare the derived class like so
TMyBaseClassReader<T: TMyBaseClass, constructor>

How to create a custom enumerator for a class derived from TDictionary?

I have defined a collection derived from TDictionary, and need to define a custom enumerator that apply an additional filter.
I'm stuck as I can't access the TDictionary FItems array (it is private) so I can't define the MoveNext method
How would you proceed to redefine a filtered enumerator on a class derived from TDictionary?
Here's a simple code to illustrate what I want to do:
TMyItem = class(TObject)
public
IsHidden:Boolean; // The enumerator should not return hidden items
end;
TMyCollection<T:TMyItem> = class(TDictionary<integer,T>)
public
function GetEnumerator:TMyEnumerator<T>; // A value filtered enumerator
type
TMyEnumerator = class(TEnumerator<T>)
private
FDictionary: TMyCollection<integer,T>;
FIndex: Integer;
function GetCurrent: T;
protected
function DoGetCurrent: T; override;
function DoMoveNext: Boolean; override;
public
constructor Create(ADictionary: TMyCollection<integer,T>);
property Current: T read GetCurrent;
function MoveNext: Boolean;
end;
end;
function TMyCollection<T>.TMyEnumerator.MoveNext: Boolean;
begin
// In below code, FIndex is not accessible, so I can't move forward until my filter applies
while FIndex < Length(FDictionary.FItems) - 1 do
begin
Inc(FIndex);
if (FDictionary.FItems[FIndex].HashCode <> 0)
and not(FDictionary.FItems[FIndex].IsHidden) then // my filter
Exit(True);
end;
Result := False;
end;
You can base your Enumerator on TDictionary's enumerator, so you don't actually need access to FItems. This works even if you write a wrapper class around TDictionary as Barry suggests. The enumerator would look like this:
TMyEnumerator = class
protected
BaseEnumerator: TEnumerator<TPair<Integer, T>>; // using the key and value you used in your sample
public
function MoveNext:Boolean;
property Current:T read GetCurrent;
end;
function TMyEnumerator.MoveNext:Boolean;
begin
Result := BaseEnumerator.MoveNext;
while Result and (not (YourTestHere)) do // ie: the base enumerator returns everything, reject stuff you don't like
Result := BaseEnumerator.MoveNext;
end;
function TMyEnumerator.Current: T;
begin
Result := BaseEnumerator.Current.Value; // Based on your example, it's value you want to extract
end;
And here's a complete, 100 lines console application that demonstrates this:
program Project23;
{$APPTYPE CONSOLE}
uses
SysUtils, Generics.Collections;
type
TMyType = class
public
Int: Integer;
constructor Create(anInteger:Integer);
end;
TMyCollection<T:TMyType> = class(TDictionary<integer,T>)
strict private
type
TMyEnumerator = class
protected
BaseEnum: TEnumerator<TPair<Integer,T>>;
function GetCurrent: T;
public
constructor Create(aBaseEnum: TEnumerator<TPair<Integer,T>>);
destructor Destroy;override;
function MoveNext:Boolean;
property Current:T read GetCurrent;
end;
public
function GetEnumerator: TMyEnumerator;
end;
{ TMyCollection<T> }
function TMyCollection<T>.GetEnumerator: TMyEnumerator;
begin
Result := TMyEnumerator.Create(inherited GetEnumerator);
end;
{ TMyType }
constructor TMyType.Create(anInteger: Integer);
begin
Int := anInteger;
end;
{ TMyCollection<T>.TMyEnumerator }
constructor TMyCollection<T>.TMyEnumerator.Create(aBaseEnum: TEnumerator<TPair<Integer, T>>);
begin
BaseEnum := aBaseEnum;
end;
function TMyCollection<T>.TMyEnumerator.GetCurrent: T;
begin
Result := BaseEnum.Current.Value;
end;
destructor TMyCollection<T>.TMyEnumerator.Destroy;
begin
BaseEnum.Free;
inherited;
end;
function TMyCollection<T>.TMyEnumerator.MoveNext:Boolean;
begin
Result := BaseEnum.MoveNext;
while Result and ((BaseEnum.Current.Value.Int mod 2) = 1) do
Result := BaseEnum.MoveNext;
end;
var TMC: TMyCollection<TMyTYpe>;
V: TMyType;
begin
try
TMC := TMyCollection<TMyType>.Create;
try
// Fill TMC with some values
TMC.Add(1, TMyType.Create(1));
TMC.Add(2, TMyType.Create(2));
TMC.Add(3, TMyType.Create(3));
TMC.Add(4, TMyType.Create(4));
TMC.Add(5, TMyType.Create(5));
TMC.Add(6, TMyType.Create(6));
TMC.Add(7, TMyType.Create(7));
TMC.Add(8, TMyType.Create(8));
// Filtered-enum
for V in TMC do
WriteLn(V.Int);
ReadLn;
finally TMC.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
You should write a class that wraps TDictionary rather than inherits from it directly. The only reason TDictionary can be inherited from at all is so that TObjectDictionary could be defined and stay polymorphic with it. That is, the only proper support through overriding TDictionary is to customize what happens when keys and values are removed from the dictionary (so they might need to be freed).

How to link "parallel" class hierarchy?

I've got a little class hierarchy where each class corresponds to a certain TComponent descendent (say base class TDefaultFrobber with descendents TActionFrobber and TMenuItemFrobber, corresponding to TComponent, TCustomAction and TMenuItem, respectively). Now I want a factory (?) function something like this:
function CreateFrobber(AComponent: TComponent): IFrobber;
begin
if AComponent is TCustomAction then
Result := TActionFrobber.Create(TCustomAction(AComponent))
else if AComponent is TMenuItem then
Result := TMenuItemFrobber.Create(TMenuItem(AComponent))
else
Result := TDefaultFrobber.Create(AComponent);
end;
Can I somehow refactor this to use virtual functions or something similar instead of the if-else cascade or RTTI?
Edit: My solution for now:
unit Frobbers;
interface
uses
Classes;
type
IComponentFrobber = interface
end;
TComponentFrobberClass = class of TComponentFrobber;
TComponentFrobber = class(TInterfacedObject, IComponentFrobber)
strict private
FComponent: TComponent;
protected
constructor Create(AComponent: TComponent);
property Component: TComponent read FComponent;
public
class function FindFrobberClass(AComponentClass: TComponentClass): TComponentFrobberClass; overload; static;
class function FindFrobberClass(AComponent: TComponent): TComponentFrobberClass; overload; static;
class procedure RegisterFrobber(AComponentClass: TComponentClass; AFrobberClass: TComponentFrobberClass); static;
end;
implementation
uses
ActnList,
Menus;
type
TComponentFrobberRegistryItem = record
ComponentClass: TComponentClass;
FrobberClass: TComponentFrobberClass;
end;
var
FComponentFrobberRegistry: array of TComponentFrobberRegistryItem;
class function TComponentFrobber.FindFrobberClass(AComponentClass: TComponentClass): TComponentFrobberClass;
var
i: Integer;
begin
// Search backwards, so that more specialized frobbers are found first:
for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do
if FComponentFrobberRegistry[i].ComponentClass = AComponentClass then
begin
Result := FComponentFrobberRegistry[i].FrobberClass;
Exit;
end;
Result := nil;
end;
constructor TComponentFrobber.Create(AComponent: TComponent);
begin
inherited Create;
FComponent := AComponent;
end;
class function TComponentFrobber.FindFrobberClass(AComponent: TComponent): TComponentFrobberClass;
var
i: Integer;
begin
// Search backwards, so that more specialized frobbers are found first:
for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do
if AComponent is FComponentFrobberRegistry[i].ComponentClass then
begin
Result := FComponentFrobberRegistry[i].FrobberClass;
Exit;
end;
Result := nil;
end;
class procedure TComponentFrobber.RegisterFrobber(AComponentClass: TComponentClass;
AFrobberClass: TComponentFrobberClass);
var
i: Integer;
begin
Assert(FindFrobberClass(AComponentClass) = nil, 'Duplicate Frobber class');
i := Length(FComponentFrobberRegistry);
SetLength(FComponentFrobberRegistry, Succ(i));
FComponentFrobberRegistry[i].ComponentClass := AComponentClass;
FComponentFrobberRegistry[i].FrobberClass := AFrobberClass;
end;
function CreateComponentFrobber(AComponent: TComponent): IComponentFrobber;
var
FrobberClass: TComponentFrobberClass;
begin
FrobberClass := TComponentFrobber.FindFrobberClass(AComponent);
Assert(FrobberClass <> nil);
Result := FrobberClass.Create(AComponent);
end;
type
TActionFrobber = class(TComponentFrobber);
TMenuItemFrobber = class(TComponentFrobber);
initialization
TComponentFrobber.RegisterFrobber(TCustomAction, TActionFrobber);
TComponentFrobber.RegisterFrobber(TMenuItem, TMenuItemFrobber);
end.
Thanks to Cesar, Gamecat and mghie.
If you create a class with a virtual constructor and create a class type for that class. You can create a lookuplist based on the component class name.
Example:
type
TFrobber = class
public
constructor Create; virtual;
class function CreateFrobber(const AComponent: TComponent): TFrobber;
end;
TFrobberClass = class of TFrobber;
type
TFrobberRec = record
ClassName: ShortString;
ClassType: TFrobberClass;
end;
const
cFrobberCount = 3;
cFrobberList : array[1..cFrobberCount] of TFrobberRec = (
(ClassName : 'TAction'; ClassType: TActionFrobber),
(ClassName : 'TButton'; ClassType: TButtonFrobber),
(ClassName : 'TMenuItem'; ClassType: TMenuItemFrobber)
);
class function TFrobber.CreateFrobber(const AComponent: TComponent): TFrobber;
var
i : Integer;
begin
Result := nil;
for i := 1 to cFrobberCount do begin
if AComponent.ClassName = cFrobberList[i].ClassName then begin
Result := cFrobberList[i].ClassType.Create();
Exit;
end;
end;
end;
You can of course also work with a dynamic list (dictionary) but then you must register each combination somehow.
Update
To commnent on the remarks of mghie.
You are perfectly right. But this is not possibly without really ugly tricks.
Right now you have to use the initialization/finalization sections of a unit to regoister a class. But it would be cool to add a initialization/finalization class method to a class. These have to be called along with the initialization (and finalization) of the unit. Like this:
class
TFrobber = class
private
initialization Init; // Called at program start just after unit initialization
finalization Exit; // called at program end just before unit finalization.
end;
2 suggestions:
Make class pair array of classes, then you can get the Index and use the pair of the class constructor,
var
ArrayItem: array[0..1] of TComponentClass = (TActionFrobber, TMenuItemFrobber);
ArrayOwner: array[0..1] of TComponentClass = (TCustomAction, TMenuItem);
function CreateFrobber(AComponent: TComponentClass): IFrobber;
var
Index: Integer;
begin
Result:= nil;
for I := Low(ArrayOwner) to High(ArrayOwner) do
if AComponent is ArrayOwner[I] then
begin
Result:= ArrayItem[I].Create(AComponent);
Break;
end;
if Result = nil then
Result:= TDefaultFrobber.Create(AComponent);
end;
or use RTTI + ClassName conventions, like this:
function CreateFrobber(AComponent: TComponentClass): IFrobber;
const
FrobberClassSuffix = 'Frobber';
var
LClass: TComponentClass;
LComponent: TComponent;
begin
LClass:= Classes.FindClass(AComponent.ClassName + FrobberClassSuffix);
if LClass <> nil then
LComponent:= LClass.Create(AComponent)
else
LComponent:= TDefaultFrobber.Create(AComponent);
if not Supports(LComponent, IFrobber, Result) then
Result:= nil;
end;
I'd like to add some comments to your current solution, answering here as this can not really be done in the comments section:
type
IComponentFrobber = interface
end;
TComponentFrobberClass = class of TComponentFrobber;
TComponentFrobber = class(TInterfacedObject, IComponentFrobber)
strict private
FComponent: TComponent;
protected
constructor Create(AComponent: TComponent);
property Component: TComponent read FComponent;
public
class function FindFrobberClass(AComponentClass: TComponentClass):
TComponentFrobberClass; overload; static;
class function FindFrobberClass(AComponent: TComponent):
TComponentFrobberClass; overload; static;
class procedure RegisterFrobber(AComponentClass: TComponentClass;
AFrobberClass: TComponentFrobberClass); static;
end;
There is not much point in using TInterfacedObject for the base class, as you will always need the object, not the interface it implements - how else would you find your concrete Frobber class? I would split this into TComponentFrobber, descending from TInterfacedObject, and a TComponentRegistry class (descending from TObject) that has the class methods. You can then of course make the registry class more generic, it is not tied to TComponentFrobber and could be reused.
Edit: I have used similar class registries for example when loading files: load the identifier for the next object (could be for example string, integer or GUID), then get the correct class to instantiate from the registry, then create and load the object.
type
TComponentFrobberRegistryItem = record
ComponentClass: TComponentClass;
FrobberClass: TComponentFrobberClass;
end;
var
FComponentFrobberRegistry: array of TComponentFrobberRegistryItem;
This is OK if you will never add or remove classes to / from the registry, but generally I would not use an array but a list for the registry entries.
class function TComponentFrobber.FindFrobberClass(AComponentClass: TComponentClass):
TComponentFrobberClass;
var
i: Integer;
begin
// Search backwards, so that more specialized frobbers are found first:
for i := High(FComponentFrobberRegistry) downto Low(FComponentFrobberRegistry) do
if FComponentFrobberRegistry[i].ComponentClass = AComponentClass then
begin
Result := FComponentFrobberRegistry[i].FrobberClass;
Exit;
end;
Result := nil;
end;
Searching backwards in the array will not help for finding the most specialized frobber, unless you add them in the correct order (least specialized first). Why don't you check for the ClassType being equal? There is also ClassParent to traverse the class hierarchy, if you need to test for base classes too.

Resources