Delphi泛型:如何规范“引用自己类型的类”

时间:2013-09-22 02:51:58

标签: delphi generics collections copy

在Delphi XE2中,我想编写一个泛型集合类来操作必须具有Copy(owntype)方法的对象,但我无法弄清楚如何最好地声明它。

我想要这样的例子(为了简单起见,一个项目的集合):

//------ 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>;

关键问题是在Col中,我需要MyCollection的通用实现来查找TMyCopyable.Copy。不出所料,过载或虚拟都不能完成这项任务:

  • 有了重载,代码会编译,但MyCollection.GetItem会找到 TBaseCopyable.Copy,而不是TMyCopyable.Copy。
  • 使用virtual / override 因为两个Copy声明的签名而无法编译 不匹配。

所以我认为我需要在TBaseCopyable的规范中以某种方式使用泛型,可能代替继承。但我不确定如何,主要是因为我不特别需要将类型参数提供给TBaseCopyable,我只需要复制参数类型以通用方式引用“它自己类的类型”。

想法?谢谢!

2 个答案:

答案 0 :(得分:6)

TBaseCopyable转换为Generic类并将其Generic类型应用于Copy(),然后TMyCopyable可以覆盖它,例如:

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;

或者,只需执行与TPersistent.Assign()相同的操作(因为它不使用泛型):

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;

答案 1 :(得分:0)

回答我自己的问题,或者至少总结一下结果:

据我所知,在我提出问题时,问题没有完整的答案。我所学到的是:

[1]如果基类项(此处为TBaseCopyable)没有状态,并且要么是抽象的,要么方法不需要引用相同类型的其他对象,所以Remy的解决方案是可行的方法。 (例如:TBaseCopyable没有字段,只有抽象方法。)

[2]一个重要的问题是如何指定一个泛型类,其后代类可以指定方法参数并返回与其封闭类相同类型的值。在Remy的例子中,这是在后代类声明中完成的:

      TMyCopyable = class(TBaseCopyable<TMyCopyable>)

这意味着在泛型类中,T将被最终的兴趣类替换。

[3]但是,在TBaseCopyable的通用声明中,T始终是TBaseCopyable的信息不可用,因此在TBaseCopyable的实现中,对类型为T的对象的引用将无法看到TBaseCopyable的方法或字段。

如果我们可以在T上设置约束来告诉编译器T是TBaseCopyable,那么这将得到解决。

这显然是C#中的方法:  http://blogs.msdn.com/b/ericlippert/archive/2011/02/03/curiouser-and-curiouser.aspx

在Delphi中,我认为会是这样的:

  type
    TBaseCopyable<T: TBaseCopyable<T> > = class
    ...
像雷米为MyCollection展示的那样。但是,该语法在同一个类声明中是不合法的(错误:未声明的标识符TBaseCopyable),因为TBaseCopyable尚未完全定义。我们可能会考虑为TBaseCopyable创建一个前向声明(就像我们对非泛型类一样),但这会引发错误,显然编译器不支持它:

[4]也许泛型类可以继承实现?

如果我们这样做了:

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

这将允许TBaseCopyable具有可以相互引用的一些字段和方法。但是,即使这些方法是虚拟的,它们也会对后代强加固定的参数/返回类型,避免使用泛型的基本原理。

因此,此策略仅适用于不需要专门处理后代类型的字段和方法...例如对象计数器。

结论

这个问题最近引起了人们对“奇怪的反复出现的模板模式”的关注:http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern。即使看起来人们想要完成的事情很简单,但幕后仍有理论问题。

这种情况似乎要求一个语言关键字,意思是“与我的封闭类相同的类型”。然而,这显然会导致协方差/逆变问题 - 违反哪些类型的规则可以替代继承层次中的哪些类型。也就是说,似乎Delphi没有像C#那样允许部分解决方案。

当然,我很高兴得知有更进一步的方法!

哦,我并不觉得太难以挣扎到底 - 甚至肯阿诺德认为这很困难:https://weblogs.java.net/blog/arnold/archive/2005/06/generics_consid.html#comment-828994

: - )