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.
Related
I have to develop a game in Lazarus for school, and I ran into an error that I can't find a solution for.
I have a dynamic array where I want to store classes in so that I can call procedures on those classes.
TKarte is the ancestor class, and I have many different classes (all representing different Cards) that have the same procedures as the ancestor class.
unit Karten;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, Dialogs, ExtCtrls;
type
TKarte=class
public
class procedure GetPicture(Objekt:TImage);virtual;
class procedure OnPlay;virtual;
end;
type
Karte = class(TKarte)
public
class procedure GetPicture(Objekt:TImage);override;
class procedure OnPlay;override;
end;
type
Karte2 = class(TKarte)
public
class procedure GetPicture(Objekt:TImage);override;
class procedure OnPlay;override;
end;
implementation
class procedure Karte.OnPlay();
begin
ShowMessage(ClassName);
end;
class procedure Karte.GetPicture(Objekt:Timage);
begin
Objekt.Picture.LoadFromFile('Grafiken\Karten\Mindcontrol.png');
end;
class procedure Karte2.GetPicture(Objekt:Timage);
begin
Objekt.Picture.LoadFromFile('Grafiken\Karten\Mindcontrol.png');
end;
class procedure Karte2.OnPlay();
begin
ShowMessage(Karte2.ClassName);
end;
class procedure TKarte.OnPlay();
begin
ShowMessage(ClassName);
end;
class procedure TKarte.GetPicture(Objekt:TImage);
begin
Objekt.Picture.LoadFromFile('Grafiken\Sprites\Buttons\Button 1.png');
end;
end.
This is how I add them and call them from the array at the moment:
Hand: array of Class of TKarte;
procedure TSplashScreen.Button2Click(Sender: TObject);
begin
SetLength(Hand,Length(Hand)+1);
Hand[High(Hand)] := Karte;
Hand[High(Hand)].OnPlay();
Hand[High(Hand)].GetPicture(Image1);
end;
There is no problem with running the program, but when I try to add a new component, or I press CTRL + Space for the Auto-Complete, it gives me an error at the declaration of the array:
Error: Anonymous Class definitions are not allowed
I have tried to find an answer to this problem, but there seems to be noone with the same problem :(
Can somebody help me?
Offhand, I see nothing wrong with the code, and as you said the code does run correctly. It is only the IDE that is having a problem with it. As such, I would not suggest declaring the array's element type directly in the array's declaration. I would suggest declaring an alias for it before declaring the array, eg:
type
TKarte=class
...
end;
TKarteClass = Class of TKarte;
...
Hand: array of TKarteClass;
There are few problems in your code.
When creating array of some type you don't define the said type in the array itself but only tell which type needs to be used. So your array definition would be:
Hand: array of TKarte;
I also see that you have declared all your procedures as class procedure. There is a fundamental difference between class methods and ordinary methods. Most likely you won't need them to be declared as class methods based on what you are trying to achieve. While I could not find suitable Lazarus documentation on this topic you may refer to Delphi documentation on Class methods to get better understanding about their difference.
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>;
In Delphi XE2, I want to write a generic collection class which manipulates objects which must have a Copy(owntype) method, but I can't figure out how best to declare this.
I want something like this example (a collection of one item, for simplicity):
//------ Library ------
Type
TBaseCopyable = class
S: string;
// procedure Copy(OtherObject: TBaseCopyable); overload;
procedure Copy(OtherObject: TBaseCopyable); virtual;
end;
MyCollection<T: TBaseCopyable, constructor> = class
TheItem: T;
procedure SetItem(AItem: T);
function GetItem: T;
end;
[...]
function MyCollection<T>.GetItem: T;
Var
NewItem: T;
begin
NewItem := T.Create;
NewItem.Copy(TheItem);
Result := NewItem;
end;
//------ Usage ------
Type
TMyCopyable = class(TBaseCopyable)
I: integer;
// procedure Copy(OtherObject: TMyCopyable); overload;
procedure Copy(OtherObject: TMyCopyable); override;
end;
[...]
Col: MyCollection<TMyCopyable>;
The key problem is that in Col, I need the generic implementation of MyCollection to find TMyCopyable.Copy. Unsurprisingly, neither overload or virtual do the job:
With overload, the code compiles, but MyCollection.GetItem finds
TBaseCopyable.Copy, not TMyCopyable.Copy.
With virtual/override this
doesn't compile because the signatures of the two Copy declarations
don't match.
So I figure I need to use generics somehow in the specification of TBaseCopyable, possibly instead of inheritance. But I'm not sure how, primarily because I don't particularly need to feed a type parameter into TBaseCopyable, I just need the Copy argument type to refer to "the type of it's own class" in a generic way.
Ideas? Thanks!
Turn TBaseCopyable into a Generic class and apply its Generic type to Copy(), then TMyCopyable can override it, eg:
type
TBaseCopyable<T> = class
S: string;
procedure Copy(OtherObject: T); virtual;
end;
MyCollection<T: TBaseCopyable<T>, constructor> = class
TheItem: T;
procedure SetItem(AItem: T);
function GetItem: T;
end;
type
TMyCopyable = class(TBaseCopyable<TMyCopyable>)
I: integer;
procedure Copy(OtherObject: TMyCopyable); override;
end;
Alternatively, just do the same thing that TPersistent.Assign() does (since it does not use Generics):
type
TBaseCopyable = class
S: string;
procedure Copy(OtherObject: TBaseCopyable); virtual;
end;
MyCollection<T: TBaseCopyable, constructor> = class
TheItem: T;
procedure SetItem(AItem: T);
function GetItem: T;
end;
type
TMyCopyable = class(TBaseCopyable)
I: integer;
procedure Copy(OtherObject: TBaseCopyable); override;
end;
procedure TMyCopyable.Copy(OtherObject: TBaseCopyable);
begin
inherited;
if OtherObject is TMyCopyable then
I := TMyCopyable(OtherObject).I;
end;
Answering my own question, or at least summarizing findings:
So far as I can tell, there is no complete answer to the question as I posed it. What I have learned is this:
[1] Remy's solution is the way to go if the base item class (here TBaseCopyable) has no state, and either is abstract, or methods don't need to refer to other objects of the same type. (Eg: TBaseCopyable would have no fields and only abstract methods.)
[2] A significant issue is how to specify a generic class whose descendant classes can specify method arguments and return values of the same type as their enclosing class. In Remy's example, that is accomplished in the descendant class declaration:
TMyCopyable = class(TBaseCopyable<TMyCopyable>)
This means that in the generic class, T will be replaced by the ultimate class of interest.
[3] However, within TBaseCopyable's generic declaration, the information that T is always a TBaseCopyable is not available, so in TBaseCopyable's implementation, references to objects of type T won't be able to see TBaseCopyable's methods or fields.
This would be solved if we could set a constraint on T to tell the compiler that T is a TBaseCopyable.
That's apparently the approach in C#:
http://blogs.msdn.com/b/ericlippert/archive/2011/02/03/curiouser-and-curiouser.aspx
In Delphi, I think that would go like this:
type
TBaseCopyable<T: TBaseCopyable<T> > = class
...
like Remy shows for MyCollection. However, that syntax is not legal within the same class declaration (error: undeclared identifier TBaseCopyable), because TBaseCopyable is not yet fully defined. We might think to create a forward declaration for TBaseCopyable (like we would for non-generic classes), but that throws an error, and apparently it's not supported by the compiler:
How to set a forward declaration with generic types under Delphi 2010?
E2086 error with forward declaration of a generic type http://qc.embarcadero.com/wc/qcmain.aspx?d=94044
[4] Maybe the generic class could inherit the implementation?
What if we did this:
type
TBaseCopyable<T> = class(TBaseCopyableImpl) ...
That would allow TBaseCopyable to have some fields and methods that could refer to each other. However, even if those methods were virtual, they would impose fixed argument/return types on the descendants, the avoidance of which was the rationale for using generics in the first place.
So this strategy is only good for fields and methods that don't need to specialize in the descendant types... for example an object counter.
Conclusions
This question turns out to concern the somewhat-known "Curiously recurring template pattern": http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern. Even though it seems what one is trying to accomplish is simple, there are theoretical problems behind the scenes.
The situation appears to call for a language keyword meaning something like "Same type as my enclosing class". However that apparently leads to covariance/contravariance issues -- violations of the rules of which types can substitute for which in inheritance hierachies. That said, it seems Delphi doesn't go as far as C# to permit as much of a partial solution.
Of course, I'd be happy to learn that there is a way to go further!
Oh, and I don't feel too bad struggling to get to the bottom of this -- even Ken Arnold thinks it's difficult: https://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid.html#comment-828994
:-)
I'm trying to build a generic ancestor for composite controls. The initial idea looked something like this:
type
TCompositeControl<TControl1: TControl; TControl2: TControl> = class(TWinControl)
private
FControl1,
FControl2: TControl;
public
constructor Create(AOwner: TComponent); override;
end;
TLabelAndEdit = TCompositeControl<TLabel, TEdit>; // simple example for illustration only
constructor TCompositeControl<TControl1,TControl2>.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FControl1 := TControl1.Create(Self);
FControl2 := TControl2.Create(Self);
end;
As you might already be aware, this will trigger compiler error E2568: Can't create new instance without CONSTRUCTOR constraint in type parameter declaration. Adding the constructor constraint doesn't help however as it implies a parameter-less constructor.
Casting the templates to TControl makes the code compilable:
...
FControl1 := TControl(TControl1).Create(Self);
...
...but it results in an Access Violation at runtime.
One hack that would probably work is invoking the constructor via RTTI, but I would consider that a rather dirty solution.
Another hack that essentially works is using class type variables as intermediates:
type
TControlClass = class of TControl;
constructor TCompositeControl<TControl1,TControl2>.Create(AOwner: TComponent);
var
lCtrlClass1,
lCtrlClass2: TControlClass;
begin
inherited Create(AOwner);
lCtrlClass1 := TControl1;
FControl1 := lCtrlClass1.Create(Self);
lCtrlClass2 := TControl2;
FControl2 := lCtrlClass2.Create(Self);
end;
Is there a cleaner solution? Also, can somebody explain to me why the classtype-constraint does not suffice for invoking the virtual constructor on the type parameter directly?
Your typecast is bad: TControl(TControl1).Create(Self). That tells the compiler that TControl1 is an instance of TControl, but we know that it's not an instance. It's a class reference. Type-cast it to the class-reference type instead:
FControl1 := TControlClass(TControl1).Create(Self);
An alternate syntax is
FControl1 := TControl1(TControl1.NewInstance); // get memory for object
FControl1.Create(self); // call type-specific constructor
FControl2 := TControl2(TControl2.NewInstance); // get memory for object
FControl2.Create(self); // call type-specific constructor
This is used in Delphi's Classes.pas::CreateComponent
I just can't decide which option is the least ugly!
It seems it the latest delphi version (Seattle) this compiler error is not emitted anymore. I had the same issue we an application, but only when compiled with DelphiXe8 not with delphi Seattle
If your Class uses a constructor without parameters (like TObject), I would suggest to do what the compiler says:
"Add a constructor constraint to the type-parameter declaration"
It should look something like this:
TCompositeControl < Control1: TControl, constructor; TControl2: TControl, constructor > = Class(TWinControl)
If you do so, you should be able to make the necessary call to the constructor of the generic Type.
Howether, I don't know, if it works with a constructor, who needs a parameter.
Please let us know, if it works.
I'm pretty sure it's possible to call a class and its member function dynamically in Delphi, but I can't quite seem to make it work. What am I missing?
// Here's a list of classes (some code removed for clarity)
moClassList : TList;
moClassList.Add( TClassA );
moClassList.Add( TClassB );
// Here is where I want to call an object's member function if the
// object's class is in the list:
for i := 0 to moClassList.Count - 1 do
if oObject is TClass(moClassList[i]) then
with oObject as TClass(moClassList[i]) do
Foo();
I get an undeclared identifier for Foo() at compile.
Clarification/Additional Information:
What I'm trying to accomplish is to create a Change Notification system between business classes. Class A registers to be notified of changes in Class B, and the system stores a mapping of Class A -> Class B. Then, when a Class B object changes, the system will call a A.Foo() to process the change. I'd like the notification system to not require any hard-coded classes if possible. There will always be a Foo() for any class that registers for notification.
Maybe this can't be done or there's a completely different and better approach to my problem.
By the way, this is not exactly an "Observer" design pattern because it's not dealing with objects in memory. Managing changes between related persistent data seems like a standard problem to be solved, but I've not found very much discussion about it.
Again, any assistance would be greatly appreciated.
Jeff
First of all you're doing something very unusual with TList: TList is a list of UNTYPED POINTERS. You can add any pointer you want to that list, and when you're doing moClassList.Add( TClassA ) you're actually adding a reference to the class TClassA to the list. Technically that's not wrong, it's just very unusual: I'd expect to see TClassList if you actually want to add a class! Or TList<TClass> if you're using a Delphi version that support it.
Then you're looping over the content of the list, and you're checking if oObject is of the type in the list. So you do want classes in that list after all. The test will work properly and test rather the object is of that type, but then when you do with oObject as TClass(moClassList[i]) do you're actually casting the object to... TObject. Not what you wanted, I'm sure!
And here you have an other problem: Using Foo() in that context will probably not work. TObject doesn't contain a Foo() method, but an other Foo() method might be available in context: That's the problem with the with keyword!
And to finally answer the question in the title bar: Delphi is not an Dynamic language. The compiler can't call a method it doesn't know about at compile time. You'll need to find a OOP way of expressing what you want (using simple inheritance or interfaces), or you may call the function using RTTI.
Edited after question clarification.
All your business classes need to implement some kind of notification request management, so your design benefits allot from a base class. Declare a base class that implements all you need, then derive all your business classes from it:
TBusinessBase = class
public
procedure RegisterNotification(...);
procedure UnregisterNotification(...);
procedure Foo;virtual;abstract;
end;
In your initial example you'd no longer need the list of supported classes. You'll simply do:
oObject.Foo;
No need for type testing since Delphi is strongly typed. No need for casting since you can declare oObject": TBusinessBase.
Alternatively, if you for some reason you can't change the inheritance for all your objects, you can use interfaces.
TClass is defined:
TClass = class of TObject;
You then write oObject as TClass which is effectively a null operation since oObject already was a TObject.
What you need is something like this:
type
TFoo = class
procedure Foo();
end;
TFooClass = class of TFoo;
TBar = class(TFoo)
procedure Bar();
end;
....
if oObject is TFooClass(moClassList[i]) then
with oObject as TFooClass(moClassList[i]) do
Foo();
This explains why your attempts to call Foo() does not compile, but I simply have no idea what you are trying to achieve. Even after your clarification I'm struggling to understand the problem.
Here's a really contrived example (using an array instead of a TList) that I think is what you're trying to do (error handling and try..finally intentionally omitted for clarity).
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TBaseClass=class(TObject)
procedure Foo; virtual;
end;
TClassA=class(TBaseClass)
procedure Foo; override;
end;
TClassB=class(TBaseClass)
procedure Foo; override;
end;
TClassArray= array of TBaseClass;
{ TClassB }
procedure TClassB.Foo;
begin
Writeln('TClassB.Foo() called.');
end;
{ TClassA }
procedure TClassA.Foo;
begin
Writeln('TClassA.Foo() called.');
end;
var
Base: TBaseClass;
ClassArr: TClassArray;
{ TBaseClass }
procedure TBaseClass.Foo;
begin
Writeln('TBaseClass.Foo called!!!!!!!!');
end;
begin
ClassArr := TClassArray.Create(TClassA.Create, TClassB.Create);
for Base in ClassArr do
Base.Foo;
for Base in ClassArr do
Base.Free;
ReadLn;
end.