denis
denis

Reputation: 421

Inheriting from generic's parameter doesn't work in Delphi XE

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

Upvotes: 4

Views: 1302

Answers (4)

Ondrej Kelle
Ondrej Kelle

Reputation: 37211

In Delphi XE and higher, you could also try something completely different: TVirtualMethodInterceptor.

Upvotes: 4

user743382
user743382

Reputation:

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.

Upvotes: 9

Ondrej Kelle
Ondrej Kelle

Reputation: 37211

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.

Upvotes: 0

David Heffernan
David Heffernan

Reputation: 612993

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.

Upvotes: 3

Related Questions