从通用参数继承在Delphi XE中不起作用

4

我一直在尝试通过覆盖定义在基类中的虚拟方法来扩展一堆继承自相同基类的库类。修改总是相同的,所以我决定创建一个通用类,参数为库类类型,该类继承自指定参数的类并覆盖基类的方法。

问题在于下面的代码无法编译,编译器不允许从 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.

有没有办法让这个工作起来?也许有一种方法可以欺骗编译器,使其接受等价的内容,例如继承自Dictionary<T,T>,就可以顺利编译通过。
或者,如果你有和我一样的目标,你会怎么做呢?请记住,在实际情况中,我需要重写多个方法并添加一些数据成员。
谢谢。

1
XE2和XE3中出现了相同的错误。我认为这个问题没有解决的办法。我很想知道在C#泛型或C++模板中是否可以使用相同的方法解决这个问题。这绝对是一个非常有趣的问题。 - David Heffernan
我想知道在FPc中是否可以使用类似的技巧。他们说他们的模板更接近于C++的模板 :-) - Arioch 'The
2
在C#中等价的代码会导致“error CS0689:无法从'T'派生,因为它是一个类型参数”的错误。 - David Heffernan
泛型最初是为了现在已经停止支持的Delphi for .NET而添加的,因此规则与.NET相似是有道理的。 - user743382
1
@DavidHeffernan 我知道,我正在撰写一篇更详细的答案 :) - user743382
显示剩余5条评论
4个回答

9

如您所知,这只适用于C++模板,而不适用于C#或Delphi泛型。 模板和泛型之间的根本区别在于,从概念上讲,每个模板实例化是完全单独编译的类型。 泛型被一次性编译为所有可能的类型。 当从类型参数派生时,这种做法是不可能的,因为您可能会得到诸如以下结构的构建:

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;

在C++中,这是可行的,因为在C++中,MyClass<LibraryClassA>MyClass<LibraryClassB>是完全分离的。在实例化MyClass<LibraryClassA>时,在找到基类方法之前,会查找并找到LibraryClassA中的foo
“如果你有和我一样的目标,你会怎么做呢?请记住,在真实情况下,我需要覆盖多个方法并添加一些数据成员。”
在运行时创建类型是可能的,但几乎肯定是一个极其糟糕的想法。我曾经不得不使用它一次,并且很希望避免它。它涉及读取VMT,创建其副本,将原始LibraryBaseClass.foo方法指针的副本存储在某个地方,修改VMT以指向自定义方法,并从该覆盖函数中调用原始存储的方法指针。显然没有内置的语言支持,也没有办法从代码中引用您的派生类型。
我在C#中后来也需要了这种方式,但在那种情况下,我很幸运只有四种可能的基类。我最终手动创建了四个单独的派生类,实现了四次方法,并使用查找结构(Dictionary<,>)将正确的基类映射到正确的派生类。
请注意,有一个针对特定情况的技巧,不适用于您的问题,但可能会帮助其他读者:如果您的派生类都必须实现相同的接口,并且不需要新数据成员或函数覆盖,则可以避免多次编写实现。
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);

在这种情况下,类辅助方法的实现将成为接口方法的有效实现。

谢谢,覆盖的例子很好。似乎无法覆盖LibraryClassA.foo(),而覆盖LibraryBaseClass.foo()则会创建一种全新的继承方式。 - denis
好的,不是完全“全新的类型”,但这是我不能使用非模板语法实现的东西。 - denis

4

不错,这是对我也有用的好消息 :) - user743382
很酷,我想在某个地方使用它。不幸的是,它似乎为所有虚方法引入了很多开销,而不仅仅是我想要覆盖的那些。 - denis
那怎么让你添加数据成员呢? - David Heffernan
David,这并不是问题,如果我将所有的东西移动到一个单独的类中以避免重复,并在N个继承库类中使用它,我就不需要覆盖虚拟方法并将调用转发到我的帮助类,我可以使用这个Wunderclasse自己挂钩它们。 - denis

3
你所尝试的在Delphi泛型中是不可能实现的。
对于C#泛型,相应代码也是无效的。但是,你的设计可以在C++模板中使用。

0
我可能误解了你对问题的描述,但从你提供的简化示例来看,似乎可以“扭转一下”,在层次结构中间插入一个类,像这样:
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.

我的错。我应该提到它是第三方库。 - denis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接