It would be nice to have generic property getters/setters to perform a common task on each access.
That code gives a compile-time error in Delphi XE2 'E2008 Incompatible types'. A similar code gave an internal error during compilation, but never compiles. Do I make a mistake or it is a compiler limitation?
type TFoo = class
private
function Get<T>: T;
public
property Bar: Integer read Get<Integer>;
end;
function TFoo.Get<T>: T;
begin
Result := 0;
end;
The following things can be generic in the Delphi language:
classes, e.g. TFooClass<T> = class
records, e.g. TFooRecord<T> = record
interfaces, e.g. TFooInterface<T> = interface
procedural types, e.g. TFooProc<T> = procedure
methods, e.g. procedure FooMethod<T>()
Properties cannot be generic themselves, and cannot be implemented using generic getter or setter methods.
Related
I am stuck with the following problem:
I have a class, which was generated from an xsd file using the Delphi 7 XML Data binding wizard (New -> Other -> XML Databindng).
I need to find a way to add methods to the generated code:
IXMLGlobeSettingsType = interface(IXMLNode)
['{9A8F5C55-F593-4C70-85D2-68FB97ABA467}']
{ Property Accessors }
function Get_General: IXMLGeneralSettingsType;
function Get_Projector: IXMLProjectorSettingsType;
function Get_LineMode: IXMLLineDrawingSettingsType;
{ Methods & Properties }
property General: IXMLGeneralSettingsType read Get_General;
property Projector: IXMLProjectorSettingsType read Get_Projector;
property LineMode: IXMLLineDrawingSettingsType read Get_LineMode;
//procedure SetDefault; {To be added}
end;
The interface is implemented by a corresponding class, which is also generated by the wizard:
TXMLGlobeSettingsType = class(TXMLNode, IXMLGlobeSettingsType)
protected
{ IXMLGlobeSettingsType }
function Get_General: IXMLGeneralSettingsType;
function Get_Projector: IXMLProjectorSettingsType;
function Get_LineMode: IXMLLineDrawingSettingsType;
public
procedure AfterConstruction; override;
end;
And in order to define my own extensions to the generated code, I have defined the following interface:
IDefaultable = interface
procedure SetDefault;
end;
With the following implementation class:
DefaultableXMLGlobeSettingsType = class(TXMLGlobeSettingsType, IDefaultable)
public
procedure SetDefault;
end;
However, I just realized that Delphi 7 does not let me cast one interface to another (or even from an interface to an object). So the following code will raise an error:
defaultSettings : IDefaultable;
FGlobeSettingsIntf: IXMLGlobeSettingsType; // FGlobeSettingsIntf is in fact a DefaultableXMLGlobeSettingsType
// some code
defaultSettings := FGlobeSettingsIntf as IDefaultable; // error: operator not applicable to this operand type
I am pretty much stuck here. How can get around this error? Is there a way (even an ugly one) in Delphi 7 to cast the Interface to an object and then back to another interface.
defaultSettings := FGlobeSettingsIntf as IDefaultable;
// error: operator not applicable to this operand type
This error indicates that the definition of IDefaultable does not include a GUID. Without a GUID it is not possible to query for an interface, which is what the as operator does in this context.
The as operator, when used with a interface on the right hand side, is implemented by a call to IInterface.QueryInterface. That requires a GUID to be associated with the interface.
Resolve the problem by adding a GUID when you declare IDefaultable.
This is what Supports is for:
if Supports(FGlobeSettingsIntf, IDefaultable, defaultSettings) then begin
defaultSettings.SetDefault;
end;
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 come from a vb/c# background and I am having difficulty understanding the meaning of part of the following code, specifically the bit 'self.fColConsignments.Add'
TConsignment = class(TCollectionItem)
constructor Create(Collection : TCollection); override;
...
function TIFCSUMMsg.AddConsignment: TConsignment;
begin
result := TConsignment(self.fColConsignments.Add);
end;
if you background is C#, don't missinterpret that line:
result := TConsignment(self.fColConsignments.Add);
it's just a type cast and not a constructor call. In C# it would look like:
result = (TConsignment)self.fColConsignments.Add;
Presumably fcolConsignments is a collection owned by the TIFCSUMMsg instance (Self). Add adds a new item to the collection and returns the reference as the result. The result is then cast to a TConsignment to fit the result type of the AddConsignment method.
self.fColConsignments.Add probably adds a new item into fColConsignments, which must be a collection or similar, and returns it. But the declared return type may be more generic than the actual object returned, then a typecast is applied by using TConsignment(object).
The code in your example IS NOT A CONSTRUCTOR.
In C++/C#/Java/(put your C descendant language here), constructors are nameless methods. So:
class TFoo {
TFoo() { // do something }
}
....
{
TFoo myFoo;
myFoo = new TFoo()
.....
}
This a typical construction on such languages. This is NOT how Delphi works.
Constructors in Delphi have names. The convention is that they are called .Create and
they can be static or virtual ones (like any method).
The code above can be converted to:
TFoo = class
constructor Create();
end;
...
constructor TFoo.Create()
begin
// Do something;
end;
....
// Creating an object
var
myFoo: TFoo;
begin
myFoo := TFoo.Create();
...
end;
The code you exemplified were not an constructor but a
kind of typecast.
You can get more information about this (typecasts and constructors)
in the Delphi Language Guide (or Object Pascal Language Guide, depending
on the Delphi version you have available).
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.