Delphi generics: how to specify "a class that refers to its own type",

In Delphi XE2, I want to write a generic collection class that manipulates objects that the Copy (owntype) method should have, but I can't figure out how best to declare this.

I need something like this example (a collection for 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 main problem is that in Col, I need a generic MyCollection implementation to find TMyCopyable.Copy. It is not surprising that neither overloading nor virtual work is performed:

  • With overloading, the code compiles, but MyCollection.GetItem finds TBaseCopyable.Copy, not TMyCopyable.Copy.
  • With virtual / redefinition, this does not compile because the signatures of the two Copy instances do not match.

So, I believe that I need to somehow use generics in the TBaseCopyable specification, perhaps instead of inheritance. But I'm not sure how, primarily because I don’t particularly need to feed the type parameter in TBaseCopyable, I just need the Copy argument type to refer to the “type of its own class” in a general way.

Ideas? Thanks!

+4
source share
2 answers

Turn TBaseCopyable into a Generic class and apply its generic type to Copy() , then TMyCopyable can override it, for example:

 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 as TPersistent.Assign() (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; 
+6
source

Answering my own question, or at least summarizing the conclusions:

As far as I can tell, there is no complete answer to the question as I represented it. What I found out is the following:

[1] Remy's solution is a way to go if the base element class (here TBaseCopyable) has no state and is either abstract or the methods should not refer to other objects of the same type. (For example: TBaseCopyable will not have fields and only abstract methods.)

[2] A significant problem is the definition of a generic class, whose descendant classes can specify method arguments and return values ​​of the same type as their inclusion class. In the Remy example, this is done in the declaration of the descendant class:

  TMyCopyable = class (TBaseCopyable <TMyCopyable>)

This means that in the generic class T will be replaced by a higher class of interest.

[3] However, in a TBBACopyable general declaration, information that T is always TBaseCopyable is not available, therefore, in a TBaseCopyable implementation, references to objects of type T will not be able to see TBaseCopyable methods or fields.

This would be resolved if we could set a restriction on T to tell the compiler that T is TBaseCopyable.

Apparently approach in C #: http://blogs.msdn.com/b/ericlippert/archive/2011/02/03/curiouser-and-curiouser.aspx

In Delphi, I think it will look like this:

  type
     TBaseCopyable <T: TBaseCopyable <T>> = class
     ...

as Remy for MyCollection shows. However, this syntax is not legal within the same class declaration (error: TBaseCopyable undeclared identifier), because TBaseCopyable is not yet fully defined. We might consider creating a direct declaration for TBaseCopyable (for example, for non-generic classes), but this causes an error and is apparently not supported by the compiler:

[4] Maybe a common class can inherit an implementation?

What if we did this:

  type
     TBaseCopyable <T> = class (TBaseCopyableImpl) ...

This would allow TBaseCopyable to have some fields and methods that could reference each other. Nevertheless, even if these methods were virtual, they imposed fixed arguments / return types on descendants, avoiding which was the basis for using generics in the first place.

Thus, this strategy is only good for fields and methods that should not specialize in types of children ... for example, an object counter.

conclusions

This question, it turns out, concerns the somewhat famous "Curiously Repeating Pattern Template": http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern . Despite the fact that it seems that trying to achieve is simple, there are theoretical problems behind the scenes.

The situation seems to trigger a language keyword meaning something like "The same type as my encompassing class." However, this, apparently, leads to covariance / contravariant issues - violations of the rules, the types of which can replace them in hereditary hierarchies. However, it seems that Delphi is not getting to C # to allow as much of a partial solution as possible.

Of course, I would be glad to know that there is a way to move on!

Oh, and I don’t feel too bad trying to figure it out - even Ken Arnold finds it difficult: https://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid.html#comment-828994

:-)

0
source

All Articles