Reputation: 635
In Delphi 10 Seattle I want to make TProject class with structure like this:
// TProject
// Frames[]:TFrame -- all frames in project folder
// Sounds[]:TSound -- all sounds in project folder
// WorkSets[]:TWorkSet -- frames subsets for some kind of works
// WorkSetFrames[]:TWorkSetFrame
// Clips[]:TClip -- recorded clips
// ClipFrames[]:TClipFrame
and I want to have not only forward link from project to a collection and from collection to item. Also, I want to have backlinks from each final item to their owned collection and from each collection to their owner object.
Main request - all links must be correctly typed:
It must fill by constructors and obtain it from that basic generic classes.
And next - it must be overridable, to allow me to add property Name to generic collection item descendant and FindByName function to generic collection class and obtain a possibility to instantiate some of my collections from this two new generic classes. For example, to make TFrame and TFrameCollection with this feature.
It can be based on TCollection or TList, but it all don't have all the required features.
I have tried some declarations, but it led to class incompatibility errors (https://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29 problem noted in explanations).
UPD 1:
The last attempt to solve the problem of usage generic collection and generic collection item classes which use each other and allow override it led me to this code:
unit GenericCollection;
interface
uses Generics.Collections;
type
TGenericCollectionItem<TCollectionOwner: class> = class
public
type
TCollection = class(TList<TGenericCollectionItem<TCollectionOwner>>)
private
FOwner: TCollectionOwner;
public
property Owner: TCollectionOwner read FOwner;
constructor Create(AOwner: TCollectionOwner);
end;
private
FOwnerCollection: TCollection;
function GetIndex: Integer;
procedure SetIndex(const Value: Integer);
public
property Index: Integer read GetIndex write SetIndex;
property OwnerCollection: TCollection read FOwnerCollection;
end;
TNamedGenericCollectionItem<TCollectionOwner: class> = class(TGenericCollectionItem<TCollectionOwner>)
public
type
TNamedGenericCollection = class(TCollection)
public
function FindItemName(AName: string): TNamedGenericCollectionItem<TCollectionOwner>;
end;
private
FName: string;
public
property Name: string read FName write FName;
end;
implementation
{ TGenericCollectionItem<ACollectionOwnerType> }
function TGenericCollectionItem<TCollectionOwner>.GetIndex: Integer;
begin
Result := OwnerCollection.IndexOf(Self);
end;
procedure TGenericCollectionItem<TCollectionOwner>.SetIndex(
const Value: Integer);
var
CurIndex: Integer;
begin
CurIndex := GetIndex;
if (CurIndex >= 0) and (CurIndex <> Value) then
FOwnerCollection.Move(CurIndex, Value);
end;
{ TGenericCollectionItem<ACollectionOwnerType>.TCollection }
constructor TGenericCollectionItem<TCollectionOwner>.TCollection.Create(
AOwner: TCollectionOwner);
begin
inherited Create;
FOwner := AOwner;
end;
{ TNamedGenericCollectionItem<TCollectionOwner>.TNamedGenericCollection }
function TNamedGenericCollectionItem<TCollectionOwner>.TNamedGenericCollection.FindItemName(
AName: string): TNamedGenericCollectionItem<TCollectionOwner>;
var
X: TGenericCollectionItem<TCollectionOwner>;
begin
// TODO: Use hash-based index
for X in Self do
if TNamedGenericCollectionItem<TCollectionOwner>(X).Name = AName then
Exit(TNamedGenericCollectionItem<TCollectionOwner>(X));
end;
end.
But when I use it by declaring
TFrame = class(TNamedGenericCollectionItem<TProject>)
end;
and adding to TProject
FFrames: TFrame.TNamedGenericCollection;
this call in TProject's constructor
FFrames := TFrame.TNamedGenericCollection.Create(Self);
still geave me annoying exception:
[dcc32 Error] ProjectInfo.pas(109): E2010 Incompatible types:
'TNamedGenericCollectionItem<TCollectionOwner>.TNamedGenericCollection' and
'GenericCollection.TNamedGenericCollectionItem<ProjectInfo.TProject>.TNamedGenericCollection'
What I can do to solve this?
Upvotes: 0
Views: 255
Reputation: 6013
I really think that you are over-thinking this. Once I got my head round what you are trying to do, you just want the owner to know what sort its items are (which TObjectList< T > for example already knows) and for the item to know what its owner is, which is easily constructed.
unit Unitgenerics;
interface
uses
System.Generics.Collections;
type
TGItem<TOwner : class> = class
private
fOwner: TOwner;
public
constructor Create( const pOwner : TOwner );
property Owner : TOwner
read fOwner;
end;
TRealItem = class;
TRealOwner = class( TObjectList<TRealItem> )
// Items are already of type TRealItem
end;
TRealItem = class(TGItem< TRealOwner > )
// Owner already defined and is of type TRealOwner
end;
implementation
{ TGItem<TOwner> }
constructor TGItem<TOwner>.Create(const pOwner: TOwner);
begin
inherited Create;
fOwner := pOwner;
end;
end.
To extend this down to descendants, that is easy enough for the items if the owner didn't change. But it does. However all we need to do is use generics to reflect to the descendants how the owner changes - like this
unit Unitgenerics;
interface
uses
System.Generics.Collections;
type
TGItem<TOwner : class> = class
private
fOwner: TOwner;
public
constructor Create( const pOwner : TOwner );
property Owner : TOwner
read fOwner;
end;
TRealItem< TOwner : class > = class;
TRealOwner<TOwner : class> = class( TObjectList<TRealItem< TOwner >> )
// Items are already of type TRealItem<TOwner>
end;
TRealItem< TOwner : class > = class(TGItem< TRealOwner<TOwner> > )
// Owner already defined and is of type TRealOwner<TOwner>
end;
implementation
{ TGItem<TOwner> }
constructor TGItem<TOwner>.Create(const pOwner: TOwner);
begin
inherited Create;
fOwner := pOwner;
end;
end.
This is how to extend further without nesting generics too far...
unit Unitgenerics;
interface
uses
System.Generics.Collections;
type
TGItem<TOwner : class> = class
private
fOwner: TOwner;
public
constructor Create( const pOwner : TOwner );
property Owner : TOwner
read fOwner;
end;
TRealItem< TOwner : class > = class;
TRealOwner<TOwner : class> = class( TObjectList<TRealItem< TOwner >> )
// Items are already of type TRealItem
// add some properties here
end;
TRealItem< TOwner : class > = class(TGItem< TRealOwner<TOwner> > )
// Owner already defined and is of type TRealOwner
// add some properties here
end;
T2ndLevelItem = class;
T2ndLevelOwner = class;
T2ndLevelOwner = class( TRealOwner< T2ndLevelOwner > )
end;
T2ndLevelItem = class( TRealItem< T2ndLevelOwner > )
end;
TInheritable2ndLevelItem< TOwner : class> = class;
TInheritable2ndLevelOwner< TOwner : class> = class;
TInheritable2ndLevelOwner< TOwner : class> = class( TRealOwner< TOwner > )
end;
TInheritable2ndLevelItem< TOwner : class> = class( TRealItem< TOwner > )
end;
T3rdLevelItem = class;
T3rdLevelOwner = class;
T3rdLevelOwner = class( TRealOwner< T3rdLevelOwner > )
end;
T3rdLevelItem = class( TRealItem< T3rdLevelOwner > )
end;
TInheritable3rdLevelItem< TOwner : class> = class;
TInheritable3rdLevelOwner< TOwner : class> = class;
TInheritable3rdLevelOwner< TOwner : class> = class( TInheritable2ndLevelOwner< TOwner > )
end;
TInheritable3rdLevelItem< TOwner : class> = class( TInheritable2ndLevelItem< TOwner > )
end;
implementation
{ TGItem<TOwner> }
constructor TGItem<TOwner>.Create(const pOwner: TOwner);
begin
inherited Create;
fOwner := pOwner;
end;
end.
I updated your example using my principles. In the process I realised that your main problem was dealing with the fact that the top level item is, in fact, a list. You are trying to explain this to the compiler with your convoluted constructs, but that is not the way to do it.
interface
uses
System.Generics.Collections;
type
TGenericCollectionItem< TOwnerCollection : class > = class
private
FOwnerCollection: TList<TOwnerCollection>;
function GetIndex: Integer;
procedure SetIndex(const Value: Integer);
public
constructor Create(pOwnerCollection: TList<TOwnerCollection>);
property Index: Integer read GetIndex write SetIndex;
property OwnerCollection: TList<TOwnerCollection> read FOwnerCollection;
end;
TNamedGenericCollectionItem<TOwnerCollection: class> = class(TGenericCollectionItem< TOwnerCollection>)
public
private
FName: string;
public
property Name: string read FName write FName;
end;
type
TFrames = class;
TProject = class( TList< TFrames > )
private
FFrames : TFrames;
public
constructor Create;
end;
TFrames = class(TNamedGenericCollectionItem< TFrames>)
end;
implementation
{ TGenericCollectionItem<ACollectionOwnerType> }
constructor TGenericCollectionItem<TOwnerCollection>.Create(
pOwnerCollection: TList<TOwnerCollection>);
begin
inherited Create;
pOwnerCollection := fOwnerCollection;
end;
function TGenericCollectionItem<TOwnerCollection>.GetIndex: Integer;
begin
Result := OwnerCollection.IndexOf(Self);
end;
procedure TGenericCollectionItem<TOwnerCollection>.SetIndex(
const Value: Integer);
var
CurIndex: Integer;
begin
CurIndex := GetIndex;
if (CurIndex >= 0) and (CurIndex <> Value) then
FOwnerCollection.Move(CurIndex, Value);
end;
{ TProject }
constructor TProject.Create;
begin
inherited Create;
FFrames:= TFrames.Create( self );
end;
I hope that this helps.
Upvotes: 1