How to extend a Generic Collection in Delphi? - delphi

I have a Generic collection like this:
TFoo = class;
TFooCollection<T: TFoo> = class(TObjectDictionary<string, T>)
procedure DoSomething;
end;
It works fine.
Now I need to extend TFooCollection like this:
TBar = class( TFoo );
TBarCollection<T: TBar> = class(TFooCollection)
procedure DoSomethingElse;
end;
And the compiler complains that TFooCollection isn't defined.
As TBar is inheriting from TFoo, I would like to take advantage of TFooCollection methods (that would work with TFoo and TBar items) and do something else just with TBar Collections.
Is it possible in Delphi?

You knew how to extend the generic collection TObjectDictionary, so simply apply that same technique when extending the generic collection TFooCollection. TObjectDictionary doesn't specify a type by itself — you needed to provide values for its two generic type parameters. One you hard-coded to string, and the other you provided by forwarding the generic type parameter received in TFooCollection.
Likewise, when specifying the base type for TBarCollection, you can either provide a hard-coded value for the TFooCollection type parameter, or you can forward the parameter from TBarCollection. You probably want to do the latter:
type
TBarCollection<T: TBar> = class(TFooCollection<T>)
end;

Remember that in generic classes, the type declarations are part of the type name.
The compiler is quite correct is stating that you do not have a TFooCollection type defined. You have defined a type called TFooCollection (or TFooCollection).

Related

Multiple generation of Obj with a generic class

I'm having some trouble with generics and constructors.
I would like to have a Generic class that can handle (and create) multiple objects of the same class. Moreover, I have some code that I would like to use whatever the specific class actually is.
I thought Generics are a good solution to this, but I'm not quite sure.
type
TMultiBlock<T: IBlock, constructor> = class(TObject)
blocks: array of array of T;
constructor Create(const owner: someClass, const n: integer);
end;
constructor TMultiBlock<T>.Create(const owner: someClass, const n: integer);
var
i: integer;
begin
for i := 0 to n-1 do
T.Create();
end;
The solution above works, but the Create() that is called is not the one of the class T that I give to the Generic.
I know that I can do the Create outside the TMultiBlock class, since I know class T there, as show below:
TM := TMultiBlock<TFinestra>.Create();
for i := 0 to n do
begin
TM.blocks[i] := TFinestra.Create();
end;
Here the class TFinestre is one of the class that I want to use in the Generic. But the thing is that I want to do some common operations on the T element, and these operation will be common to whatever the T type is, so I would like to do them on the TMultiBlock.
IBlock is an interface implemented by each class of type T.
An Interface is, in OOP terms, a pure abstract definition. It can be implemented by lots of different classes. You can't create objects from an interface (unless the interface gives you a method to create an object), you can get an interface from an Object if that object supports it. As all interfaces are implemented by a class then they can do different things, but ultimately what they do is dependent on what the object is, so you are back to having to know the class.
If you want to create the objects you have to know the class of the object you are creating. The interface could provide a method for returning the class of the object which is implementing the interface, allowing you to create more of them, but then you may as well use that class.
If your different types do not have a common ancestor, then you can specify, as you have, that T must support an interface, but you still instantiate the TMutliBLOCK<T> with a class. As interfaces are always implemented by a class then T will always derive from TObject so it will always support Create. The problem is you can't call T.Create unless IBlock includes the definition of Create ...
This means that you can have a TMulitBLOCK and a TMultiBLOCK but each would be holding the objects you have declared for it. If they're not inheriting from a common class then you would not be able to restrict the type of T to that common ancestor.
You can check that the type you are using supports an interface in the constructor, and then restrict to TObject.
TMultiBLOCK<T: class> = class(TObject)
protected
blocks: TArray<T>;
public
constructor Create(AOwner: pSomeObject; nSize: Integer);
end;
constructor TMultiBLOCK<T>.Create(AOwner: pSomeObject; nSize: Integer);
begin
if(not Supports(T, IBlock)) then
raise EInvalidCast.Create('Class '+T.ClassName+' must support IBlock')
else
...
end;
To call the members of IBlock you will need to get an interface pointer for each object. Bear in mind that depending on the implementation in the different implementing classes then when the interface references go out of scope the object may delete itself. To prevent that happening you probably want to store the interface references alongside the objects when you create them so the reference count is held above 0.
If you can organise the code so that all members are derived from a common ancestor then you can restrict your TMultiBLOCK<T> to that common ancestor, rather than a common interface.

How to pass method of arbitrary type into a procedure and identify it?

I have a thread library which has three constructors for three different method types. I want them to be merged into one and some kind of logic to tell them apart inside the constructor. Is it possible? As there is TValue for values and such, is there a similar thing for method types?
I have the following types supported for now;
TAgThreadMethod1 = procedure of object;
TAgThreadMethod2 = procedure;
TAgThreadMethod3 = procedure(const AThread: TAgThread) of object;
and the constructors are like so:
constructor Create(const AOnRun: TAgThreadMethod1); overload; virtual;
constructor Create(const AOnRun: TAgThreadMethod2); overload; virtual;
constructor Create(const AOnRun: TAgThreadMethod3); overload; virtual;
For reference, I don't want the user to have the ability to change the worker method at a later time after the construction at all. So if a solution exists which can do something like this in a single constructor, is also welcome;
constructor Create
(const AOnRun: [Some type which can hold arbitrary method types]);
begin
// code to identify the method contained in AOnRun.
// if supported, assign it the correct handler.
end;
There isn't any good way to do this, because the whole point of a method pointer is to be invoked at some later point, and you can't do that unless you know its signature. So losing the distinction between signatures is very counterproductive.
If you only want to have to store one type of call inside your object, you could make the three constructors each create an anonymous method with a unified signature that wraps calls to the three types, and just store that instead of having to deal with multiple distinct method types. But what you're asking for, specifically, won't work.
I think that the best that you can do is have a constructor that accepts the most general form of the procedural type. That is TProc<TAgThread>. This will be the master constructor, the only virtual constructor. You can then delegate the other constructors to the master constructors.
To recap, the declaration in SysUtils is:
type
TProc<T> = reference to procedure(Arg1: T);
So your master constructor is:
constructor Create(AOnRun: TProc<TAgThread>); overload; virtual;
The other constructors are not virtual and might look like this:
constructor Create(AOnRun: TAgThreadMethod1); overload;
Implemented as:
constructor TMyClass.Create(AOnRun: TAgThreadMethod1);
begin
Create(
procedure(Thread: TAgThread)
begin
AOnRun();
end;
);
end;
The master constructor, the virtual one, does all the work. The other constructors, the non-virtual ones, adapt their arguments to fit that of the master constructor.
It is possible, but not with only a TValue.
Instead of passing the method itself, you could pass it as a TRttiMethod like this: MyRegisterMethod(TRttiContext.GetType(TMyClassType).GetMethod('MyMethodName'));
If you want to pass all constructors you will need to use a loop like this:
for MyMethod in TRttiContext.GetType(TMyClassType).GetMethods do
if MyMethod.IsConstructor then // As in your example you only want constructors
MyRegisterMethod(MyMethod);
Later, you can call this method like this: MyRegisteredMethod.Invoke(nil, MyMethodParams). MyMethodParams is an array of TValue, which you will need to provide. This can be stored in a TValue itself (although it might be easier to just store them in a TArray). Note that you will only want to use nil in the invoke in case of a class method.
If you want to make sure the parameters are OK, you can verify them by looking them up in MyRttiMethod.GetParameters and compare their actual types to the actual types of the TValues in the array of TValue. This can also be used to find the correct method as well.

Store Generic Array in Class using Delphi XE

Using the structure below, how can I define my TAnimalCollection class in order to store my collection? Calling either SelectAll or SelectTop10 will update the SelectedRecords.
Removing the private field allows the code to compile, but there is no mechanism to then store the returned result set.
TDog = class
private
FBreed: string;
public
property Breed: string read FBreed write FBreed;
end;
TCat = class
private
IsWild: string;
public
property IsWild: string read FIsWild write FIsWild;
end;
TMyArray<T> = array of T;
TAnimalCollection = class
private
SelectedRecords: TMyArray<T>; // Generates: Undeclared Identifier: 'T'
public
function SelectAll<T>: TMyArray<T>;
function SelectTop10<T>: TMyArray<T>;
// Other Methods
end;
First, you don't need TMyArray; the built-in TArray type does the same thing.
The compiler is correct, though. In your field declaration, there's no such thing as T. The generic argument needs to be introduced on the left of a declaration before it can be used on the right. If Delphi supported generic fields, the declaration would look like this:
SelectedRecords<T>: TArray<T>;
But it doesn't, and you wouldn't want it to in this case anyway. You apparently want to store two completely unrelated classes together in the same array simultaneously. An array is always of a single type. The only single type that unifies TDog and TCat is TObject, so your array needs to be of that type:
SelectedRecords: TArray<TObject>;
// or, more conventionally,
SelectedRecords: array of TObject;
You're welcome to declare a "generic array," but only as a field of a generic class or a variable of a generic method. If you could declare a standalone generic array, try to think of when the actual type of the array elements would be determined. If not at the point you declare the array, then when? With classes and methods, you specify the type argument(s) when you declare a variable of the class, instantiate the class, or call the method. Those are uses that are separate from their declarations, and each use is distinct. When you declare a variable, you must use it the same way you declared it — a variable's type cannot change without recompiling the program.

Property using Generics in Delphi

I'm trying to write a property which uses generics:
type TMyClass = class
protected
function GetCountBy<T: Class>: Integer;
public
property CountBy<T: Class>: Integer read GetCountBy<T>;
end;
but the compile fails on the property declaration with the message 'Property CountBy does not exist in base class', and the red squiggle on the opening < of the property name.
Is there any way to achieve this?
Edit:
Here's my other use case, which is more complex but more real world:
property ItemsBy<T: Class>[Index: Integer]: T read GetItemsBy<T> write SetItemsBy<T>;
The function filters the contents of a list to return the Index'th item of the specified class.
Generic properties are not supported in Delphi. Only generic classes, or generic methods.
I can't find anything in the documentation that explicitly states that limitation. On the other hand the documentation only describes generic classes and generic methods. And the new language grammar to support generics also makes no mention of properties.
I'm not up to speed on generics but shouldn't the declaration be more like this
type TMyClass<T: class> = class
protected
function GetCountBy<T>: Integer;
public
property CountBy<T>: Integer read GetCountBy<T>;
end;

Delphi: determine actual type of a generic?

Is there any way to determine the type of a variable passed as an argument to a method? Consider the class:
TSomeClass = class
procedure AddToList<T: TDataType; U: TListClass<T>>(Element: T; List: U);
end;
with the method implementation
procedure TSomeClass.AddToList<T, U>(Element: T; List: U);
begin
if Element is TInt then
List.AddElement(TInt.Create(XXX))
else if Element is TString then
List.AddElement(TString.Create(YYY));
end;
where TInt.Create() and TString.Create() have different sets of arguments, yet, they both inherit from TDataType.
Now, I know the is-operator can't be used like this, but is there a legal alternative that does what I'm asking here?
Not being able to use the is operator here is a known issue, but there's a pretty simple workaround.
if TObject(Element) is TInt then
List.AddElement(TInt.Create(XXX))
Also, since the type of a generic is part of the class and is known at compile-time, you might be better off restructuring your code. Make two different generic classes, one of which accepts a TInt as its <T> parameter, and the other of which accepts a TString. Put the type-specific functionality into them at that level, and have them descend from a common ancestor for shared functionality.
This question I asked some time ago
Conditional behaviour based on concrete type for generic class
might be of interest, especially if you want to use not only TObject descendants but also primitive types in your conditionals.

Resources