I have 2 classes in Delphi XE5 and pass one to another :
TfrmBaseList = class(TForm)
private
FListOwner: TSystemBaseList<TSystemColumnEntity>;
public
constructor Create(AListOwner: TSystemBaseList<TSystemColumnEntity>); virtual;
end
TSystemBaseList<T: TSystemColumnEntity> = class(TPersistent)
public
procedure Execute;
property SelectedValues: TObjectList<T> read
end;
procedure TSystemBaseList<T>.Execute;
var
frmList: TfrmBaseList;
begin
//frmList := TfrmBaseList.Create(Self<T>)
//frmList := TfrmBaseList.Create(Self<TSystemColumnEntity>)
frmList := TfrmBaseList.Create(???????)
end;
How can I Pass TSystemBaseList to Constructor of TfrmBaseList class?
This constructor only create a Form and then assign AListOwner to FListOwner,
Can I change this constructor to property like this:
TfrmBaseList = class(TForm)
private
FListOwner: TSystemBaseList<TSystemColumnEntity>;
public
property ListOwner: TSystemBaseList<TSystemColumnEntity> read FListOwner write FListOwner;
end
And how do I set it?
The constructor expects a concrete instantiation, an instance of:
TSystemBaseList<TSystemColumnEntity>
You are supplying an uninstantiated generic instance of type:
TSystemBaseList<T>
You have to supply a concrete instance to that constructor. In its current form you cannot instantiate the form from TSystemBaseList<T>.Execute.
You might think that because T must derive from TSystemColumnEntity that TSystemBaseList<T> would be compatible with TSystemBaseList<TSystemColumnEntity>. But that is not the case because there is no generic variance supported. Read more on this topic here: Generics and variance.
One way forward would be to make the form type generic too. Although that does not play well with the IDE form designer. I suspect a more radical re-design is needed to solve your problem. I won't offer advice on that re-design because I don't know the problem.
Related
I saw a code like this the other day:
type
TcxGridTableControllerAccess = class (TcxGridTableController);
TMycxGridDBTableView = class (TcxGridDBTableView)
protected
function GetViewDataClass: TcxCustomGridViewDataClass; override;
end;
TMycxGridViewData = class (TcxGridViewData)
protected
function GetFilterRowClass: TcxGridFilterRowClass; override;
end;
TMycxGridFilterRow = class (TcxGridFilterRow)
protected
procedure SetValue(Index: Integer; const Value: Variant); override;
end;
TcxGridDBTableView = class (TMycxGridDBTableView);
TMycxGridDBTableView inherited from TcxGridDBTableView that inherited from TMycxGridDBTableView. Searched for Cyclic Inheritance but only Java results.
What is this called?
Ps: I don't have the full buildable code with me.
The example code doesn't do what you think it does. You see TMycxGridDBTableView being defined as a descendant of TcxGridDBTableView, and then you see TcxGridDBTableView, defined as a descendant of TcxGridDBTableView.
However, the TcxGridDBTableView you see at the top is not the same TcxGridDBTableView that you see later. The first one refers to a class declared elsewhere, in some other unit. The next occurrence is declaring a new class in this unit that happens to have the same base name as the other unit's class.
This technique is known as an interposer class. It's used to introduce a new GetViewDataClass method, but still end up with the same class name. The form that uses controls with that name will use the new version of the class instead of the original version. It's a way to customize a VCL control without having to compile and install a custom package.
What you show is not cyclic inheritance. What happens is that dxSample.TMycxGridDBTableView inherits from a TcxGridDBTableView in another unit, probably cxGridDBTableView.TcxGridDBTableView. And dxSample.TcxGridDBtableView inherits from dxSample.TMycxGridDBTableView.
Your code is equivalent to:
type
TcxGridTableControllerAccess = class(TcxGridTableController);
{ Note: this does NOT inherit from the TcxGridDBTableView defined }
{ a little further on in the source. It inherits from the original }
{ DevEx TcxGridDBTableView. }
TMycxGridDBTableView = class(cxGridDBTableView.TcxGridDBTableView)
protected
function GetViewDataClass: TcxCustomGridViewDataClass; override;
end;
TMycxGridViewData = class(TcxGridViewData)
protected
function GetFilterRowClass: TcxGridFilterRowClass; override;
end;
TMycxGridFilterRow = class(TcxGridFilterRow)
protected
procedure SetValue(Index: Integer; const Value: Variant); override;
end;
TcxGridDBTableView = class(TMycxGridDBTableView);
So the hierarchy is:
cxGridDBTableView.TcxGridDBTableView
|
v
dxSample.TMycxGridDBTableView
|
v
dxSample.TcxGridDBTableView
So dxSample.TMycxGrdiDBTableView does not inherit from dxSample.TcxGridDBTableView, but from cxGridDBTableView.TcxGridDBTableView instead, so there is no so called cyclic inheritance there. The whole misunderstanding comes from the fact that the two classes in the different units have the same name and that the first declaration does not fully qualify the class it is inheriting from.
Now, if someone puts the unit dxSample after cxridDBTableView in his or her uses clause, then dxSample.TCxGridDBTableView is used, instead of the original DevEx class. This is called interposing.
When people want to modify the behaviour of the VCL and FireMonkey, it is not unusual to see interposer classes like
type
TVCLClass = class(OriginalVCLUnit.TVCLClass)
// modifications to the original TVCLClass
end;
or
type
TMyVCLClass = class(OriginalVCLUnit.TVCLClass)
//
end;
TVCLClass = class(TMyVCLCLass);
The code you showed does the latter.
I'm using Delphi XE to write a base class, which will allow descending classes to have dll methods mapped by applying an annotation. However I get a typecasting error, which is understandable.
In essence the base class should look like this:
TWrapperBase = class
public
FLibHandle: THandle;
procedure MapMethods;
end;
procedure TWrapperBase.MapMethods;
var
MyField: TRttiField;
MyAttribute: TCustomAttribute;
pMethod: pointer;
begin
FLibHandle := LoadLibrary(PWideChar(aMCLMCR_dll));
for MyField in TRttiContext.Create.GetType(ClassType).GetFields do
for MyAttribute in MyField.GetAttributes do
if MyAttribute.InheritsFrom(TMyMapperAttribute) then
begin
pMethod := GetProcAddress(FLibHandle, (MyAttribute as TMyMapperAttribute).TargetMethod);
if Assigned(pMethod) then
MyField.SetValue(Self, pMethod); // I get a Typecast error here
end;
And a descending class could look like this:
TDecendant = class(TWrapperBase)
private type
TSomeDLLMethod = procedure(aParam: TSomeType); cdecl;
private
[TMyMapperAttribute('MyDllMethodName')]
FSomeDLLMethod: TSomeDLLMethod;
public
property SomeDLLMethod: TSomeDLLMethod read FSomeDLLMethod;
end;
I could implement this differently, by hard coding the linking for each method in an overriden 'MapMethods'. This would however require each descendant to do so which I'd like to avoid.
I know that the TValue as used in this case will contain a pointer and not of the correct type (procedure(aParam: TSomeType); cdecl; in this case).
My question: Is there a way to pass the pointer from 'GetProcAdress' as the correct type, or to set the field directly (for example by using the field address 'PByte(Self)+MyField.Offset', which you can use to set the value of a record property)?
With the old Rtti, this could be done but only for published properties and without any type checking:
if IsPublishedProp(Self, 'SomeDLLMethod') then
SetMethodProp(Self, 'SomeDLLMethod', GetProcAddress(FLibHandle, 'MethodName');
There are two problems:
First your EInvalidCast is caused by TValue being very strict about type conversions. You are passing in a Pointer and want to set a field of type TSomeDLLMethod. You need to explicitly pass a TValue that has the correct type info.
if Assigned(pMethod) then
begin
TValue.Make(#pMethod, MyField.FieldType.Handle, value);
MyField.SetValue(Self, value);
end;
Now you will run into another EInvalidCast exception which is triggered because of a bug in XE inside the GetInlineSize method of the Rtti.pas which returns 0 for a tkProcedure kind of type. I don't know in what version this got fixed but it does not exist anymore in XE5.
For XE this can be fixed by using a unit I wrote some while ago (and which I just updated to fix this bug): RttiPatch.pas.
I also reported the original issue because Pointer is assignment compatible to a procedure type so TValue should also handle this: http://qc.embarcadero.com/wc/qcmain.aspx?d=124010
You could try something like:
Move(pMethod, PByte(Self) + Field.Offset, SizeOf(Pointer));
or
PPointer(PByte(Self) + Field.Offset)^ := pMethod;
I'm trying to implement Spring 4 Delphi and only program to interfaces instead of classes. However this seems impossible when you want to use a TObjectList.
Consider the following code:
unit Unit1;
interface
uses
Spring.Collections,
Spring.Collections.Lists;
type
IMyObjParent = interface
['{E063AD44-B7F1-443C-B9FE-AEB7395B39DE}']
procedure ParentDoSomething;
end;
IMyObjChild = interface(IMyObjParent)
['{E063AD44-B7F1-443C-B9FE-AEB7395B39DE}']
procedure ChildDoSomething;
end;
implementation
type
TMyObjChild = class(TInterfacedObject, IMyObjChild)
protected
procedure ParentDoSomething;
public
procedure ChildDoSomething;
end;
{ TMyObj }
procedure TMyObjChild.ChildDoSomething;
begin
end;
procedure TMyObjChild.ParentDoSomething;
begin
end;
procedure TestIt;
var
LMyList: IList<IMyObjChild>;
begin
TCollections.CreateObjectList<IMyObjChild>;
//[DCC Error] Unit1.pas(53): E2511 Type parameter 'T' must be a class type
end;
end.
I know I can change IMyObjChild to TMyObjChild in the example above, but if I need that in another unit or a form then how do I do this?
Trying to program only to interfaces seems too hard or impossible as soon as you need a TObjectList.
Grrr... Any ideas or help?
CreateObjectList has a generic constraint that its type parameter is a class:
function CreateObjectList<T: class>(...): IList<T>;
Your type parameter does not meet that constraint since it is an interface. The thing about an object list is that it holds objects. If you take a look at TObjectList in Spring.Collections.Lists you'll see that it also has the generic constraint that its type parameter is a class. And since CreateObjectList is going to create a TObjectList<T>, it must reflect the type constraint.
The raison d'être of TObjectList<T> is to assume ownership of its members through the OwnsObjects. Much in the same way as do the classic Delphi RTL classes of the same name. Of course you are holding interfaces and so you simply do not need this functionality. You should call CreateList instead. A plain TList<T> is what you need, even if you refer to it through the IList<T> interface.
LMyList := TCollections.CreateList<IMyObjChild>;
I've been trying to extend a bunch of library classes inheriting from the same base class by overriding a virtual method defined in that base class. The modification is always the same so instead of creating N successors of the library classes I decided to create a generic class parameterized by the library class type, which inherits from the class specified by parameter and overrides the base class' method.
The problem is that the code below doesn't compile, the compiler doesn't allow inheriting from T:
program Project1;
type
LibraryBaseClass = class
procedure foo; virtual;
end;
LibraryClassA = class(LibraryBaseClass)
end;
LibraryClassB = class(LibraryBaseClass)
end;
LibraryClassC = class(LibraryBaseClass)
end;
LibraryClassD = class(LibraryBaseClass)
end;
MyClass<T:LibraryBaseClass> = class(T) //Project1.dpr(20) Error: E2021 Class type required
procedure foo; override;
end;
procedure LibraryBaseClass.foo;
begin
end;
procedure MyClass<T>.foo;
begin
end;
begin
MyClass<LibraryClassA>.Create.foo;
MyClass<LibraryClassB>.Create.foo;
MyClass<LibraryClassC>.Create.foo;
MyClass<LibraryClassD>.Create.foo;
end.
Any ideas how to make this work? Maybe there is a way to trick the compiler into accepting something equivalent because, for example, inheriting from Dictionary<T,T> compiles without problems.
Or what would you do if you had the same goal as I? Keep in mind that in the real situation I need to override more than one method and add some data members.
Thank you
As you've been told already, this is valid with C++ templates, not with C# or Delphi generics. The fundamental difference between templates and generics is that conceptually, each template instantiation is a completely separately compiled type. Generics are compiled once, for all possible types. That simply is not possible when deriving from a type parameter, because you could get constructs such as
type
LibraryBaseClass = class
procedure foo; virtual;
end;
LibraryClassA = class(LibraryBaseClass)
procedure foo; reintroduce; virtual;
end;
LibraryClassB = class(LibraryBaseClass)
end;
MyClass<T:LibraryBaseClass> = class(T)
procedure foo; override; // overrides LibraryClass.foo or LibraryClassA.foo ?
end;
Yet this can work in C++, because in C++ MyClass<LibraryClassA> and MyClass<LibraryClassB> are completely separated, and when instantiating MyClass<LibraryClassA>, foo is looked up and found in LibraryClassA before the base class method is found.
Or what would you do if you had the same goal as I? Keep in mind that in the real situation I need to override more than one method and add some data members.
It is possible to create types at runtime, but almost certainly an extremely bad idea. I have had to make use of that once and would have loved to avoid it. It involves reading the VMT, creating a copy of it, storing a copy of the original LibraryBaseClass.foo method pointer somewhere, modifying the VMT to point to a custom method, and from that overriding function, invoking the original stored method pointer. There's certainly no built-in language support for it, and there's no way to refer to your derived type from your code.
I've had a later need for this in C# once, too, but in that case I was lucky that there were only four possible base classes. I ended up manually creating four separate derived classes, implementing the methods four times, and using a lookup structure (Dictionary<,>) to map the correct base class to the correct derived class.
Note that there is a trick for a specific case that doesn't apply to your question, but may help other readers: if your derived classes must all implement the same interface, and requires no new data members or function overrides, you can avoid writing the implementation multiple times:
type
IMySpecialInterface = interface
procedure ShowName;
end;
TMySpecialInterfaceHelper = class helper for TComponent
procedure ShowName;
end;
procedure TMySpecialInterfaceHelper.ShowName;
begin
ShowMessage(Name);
end;
type
TLabelWithShowName = class(TLabel, IMySpecialInterface);
TButtonWithShowName = class(TButton, IMySpecialInterface);
In that case, the class helper method implementation will be a valid implementation for the interface method.
In Delphi XE and higher, you could also try something completely different: TVirtualMethodInterceptor.
What you are attempting to do is simply not possible with Delphi generics.
For what it is worth, the equivalent code is also invalid in C# generics. However, your design would work with C++ templates.
I probably misunderstood your description of the problem but from your simplified example it seems you could "turn it around" and insert a class in the hierarchy in the middle like this:
program Project1;
type
LibraryBaseClass = class
procedure foo; virtual;
end;
LibraryBaseFooClass = class(LibraryBaseClass)
procedure foo; override;
end;
LibraryClassA = class(LibraryBaseFooClass)
end;
LibraryClassB = class(LibraryBaseFooClass)
end;
LibraryClassC = class(LibraryBaseFooClass)
end;
LibraryClassD = class(LibraryBaseFooClass)
end;
procedure LibraryBaseClass.foo;
begin
end;
procedure LibraryBaseFooClass.foo;
begin
end;
begin
LibraryClassA.Create.foo;
LibraryClassB.Create.foo;
LibraryClassC.Create.foo;
LibraryClassD.Create.foo;
end.
I'm trying to write a generic class which contains a generic TObjectList< T > which should contain only Elements of TItem.
uses
Generics.Collections;
type
TItem = class
end;
TGenericClass<T: TItem> = class
public
SimpleList: TList<T>; // This compiles
ObjectList: TObjectList<T>; // This doesn't compile: Compiler complaints that "T is not a class type"
end;
Is this a wrong syntax? BTW: TGenericClass< T: class> compiles, but then the Items in the List are not TItem anymore which is what I don't want.
This is a known bug with the D2009 compiler. It will most likely be fixed soon, either in an update or hotfix for 2009, or in Delphi 2010 (Weaver) once it gets released. Until then, you need some sort of workaround, unfortunately. :(
Generic types can have several constraints:
A class name, the generic type must be of that class of a descendant of the class.
An interface name, the generic type must implement that interface.
'class', the generic type must be a class (this can't be combined with a classname).
'record', the generic type must be a record.
'constructor', a bit vague, but you can create instances of the generic class type.
If you create a generic that uses other generics, you need to copy the constraints, else it won't work. In your case, TObjectList has the class constraint. This means, your T needs that constraint too.
Unfortunately this can't be combined with the named class constraint.
So I advice you to use an interface, these can be combined:
type
IItem = interface end;
TItem = class (TInterfacedObject, IItem) end;
TGenericClass<T: class, IItem> = class
private
FSimpleList: TList<T>;
FObjectList: TObjectList<T>;
end;
Besides, you should make your fields private else anyone can change them.
I like GameCat's answer (gave it +1) for the description of class constraints.
I have a slight modification of your code that works. Note that since you gave a constraint to say that T must be a descendant of TItem, you can actually just declare ObjectList as TObjectList<TItem> - no need to use T here.
Alternatively, you could create a proxy of sorts. First, note GameCat's comment about fields being private.
type
TGenericClass<T: TItem> = class
private
type
D = class(TItem); // Proxy to get your T into and object list
private
SimpleList: TList<T>;
ObjectList: TObjectList<D>; // Compiles now, but there is that type issue
public
procedure Add(Item: T); // No direct access to ObjectList
end;
Add is an example of how to access the object list. As it turns out, you can pass Item to ObjectList.Add with no trouble whatsoever:
procedure TGenericClass<T>.Add(Item: T);
begin
ObjectList.Add(Item);
end;
I think that may be a bug though, so to protect yourself against that getting fixed:
procedure TGenericClass<T>.Add(Item: T);
var
Obj: TObject;
begin
Obj := Item;
ObjectList.Add(D(Obj));
end;
Given your scenario though, I'd say TObjectList should do just fine.