如何防止通用类生成的代码重复?

4

Delphi有一个讨厌的习惯,即为泛型类复制代码。即使该代码确实相同,因为泛型类型是相似的。
我希望防止为存储不同类而产生重复。
在我的泛型容器中,我只使用Free来清理(如果需要)。

假设我有一个泛型容器如下:

unit Unit1;

interface

uses Generics.Collections;

type
  TMyContainer<T> = class(TObject)
  strict private
    FData: TList<T>;
  public
    constructor Create; virtual;
  end;

我知道 T 往往是一个对象。因为所有的对象实际上都是 TObject,所以我不希望我的容器为不同类型的对象创建重复的通用代码。
以下技巧可否防止重复?
A- 用类函数替换构造函数:
unit Unit2;

uses Unit1;

type
  TMyContainer<T> = class(Unit1.TMyContainer<T>)
  public
    class function Create: TMyContainer<T>; static;
  end;

B: 实现类函数 Create 如下:

class function TMyContainer<T>.Create: TMyContainer<T>;
var
  X: TObject;
begin
  if GetTypeKind(T) = tkClass then begin
    X:= Unit1.TMyContainer<TObject>.Create;
  end else begin
    X:= Unit1.TMyContainer<T>.Create;
  end;
  TObject(Result):= X;
end;

这个技巧是否能够防止编译器为不同类型的对象生成重复代码,还是会因为我使用了错误的假设而失败?
请注意,我不想使用非泛型存储来存储我的数据。

完整示例代码如下

unit Unit49;

interface

uses Generics.Collections;

type
  TMyContainer<T> = class(TObject)
  strict private
    FData: TList<T>;
  public
    constructor Create; virtual;
  end;

implementation

constructor TMyContainer<T>.Create;
begin
  inherited Create;
  FData:= TList<T>.Create;
end;

end.

示例程序

program Project85;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Unit49 in 'Unit49.pas';

type
  TMyContainer<T> = class(Unit49.TMyContainer<T>)
  public
    class function Create: TMyContainer<T>; static;
  end;

{ TMyContainer<T> }

class function TMyContainer<T>.Create: TMyContainer<T>;
var
  Y: T;
  X: TObject;
begin
  if GetTypeKind(T) = tkClass then begin
    X:= Unit49.TMyContainer<TObject>.Create;
  end else begin
    X:= Unit49.TMyContainer<T>.Create;
  end;
  TObject(Result):= X;
end;

var
  A: TMyContainer<TObject>;
  B: TMyContainer<TLanguages>;

begin
  A:= TMyContainer<TObject>.Create;
  B:= TMyContainer<TLanguages>.Create;
  readln;
end.

因为我有遗留代码,它不会自己移植... - Johan
1
无论如何,我认为公认的技巧在最近的Generics.Collections中得到了体现。我会阅读XE8与XE7之间的差异。Spring4d也做了类似的事情。 - David Heffernan
好的,我会去查看。 - Johan
1个回答

4
这个技巧能防止编译器为不同类型的对象生成重复代码吗?还是因为使用了错误的假设而失败了?
不行,这个方法不起作用。基本上,编译器会跟随整个类层次结构中的 T,并将其替换为特定的类型。
首先,您将有单独的 TList 代码生成,用于 TObject 和 TLanguages,因为容器声明为 FData: TList,然后您的 TrickCollection 也继承自泛型 T,TMyContainer = class(Unit49.TMyContainer),而类函数中的整个代码基本上是无用的。
编译器将为 Unit49.TMyContainer 类和 Unit49.TMyContainer 类生成重复的代码。
从您的示例中很难说出您试图避免哪些代码重复。如果容器类与您在示例中编写的一样简单,则所有代码重复都将来自 TList 类。如果您想避免这种情况,那么没有简单的方法。
您的问题的一部分源于 T 可以是任何类型。这很难优化。您可以使用 array of T 存储数据,然后委托操作函数,在其中可以使用 TObject 作为所有类的基础,并使用普通的 T 来实现其他功能。
以上可以获得多少优化也取决于您使用的 Delphi 版本,因为在最近的版本中,TList 已经使用类似的技术进行了优化。
但是,如果您可以为类和其他类型分别拥有单独的容器,则可以使用 TObjectList(甚至可以在 Windows 上使用非泛型 TObjectList)来存储所有特定类并实现一个薄包装函数,并使用类型转换用于您需要的任何类型安全函数。当然,每个这样的函数都将为每种特定类型生成一些代码,但是由于它们只是类型转换包装器,因此生成的代码不会像使用完整的 TList 那样多。
  TMyObjectContainer<T> = class(TObject)
  strict private
    FData: TObjectList<TObject>; 
  public
    constructor Create; virtual;
    destructor Destroy; override;  
    function Data(index: integer): T;
  end;

constructor TMyObjectContainer<T>.Create; 
begin
  inherited;
  FData := TObjectList<TObject>.Create;
end; 

constructor TMyObjectContainer<T>.Create; 
begin
  FData.Free;
  inherited;
end; 

function TMyObjectContainer<T>.Data(index: integer): T;
begin
  Result := T(FData.Items[index]);
end;

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