在Delphi XE中使用Generic容器 - 总是?

时间:2011-03-15 15:10:21

标签: delphi generics delphi-xe

通用容器在拥有项目时可以节省时间,并且可以节省这些项目的强类型列表。它保存了重复编码,即创建一个可能有TList内部变量的新类,以及类型化的Add / Delete类型方法,以及其他好处(例如Generic容器类提供的所有新功能)。

但是,是否建议始终将通用容器用于强类型列表?这样做的具体缺点是什么? (如果不担心代码的向后兼容性。)我昨天正在编写一个服务器应用程序并有一个项目列表,我创建了“旧方法”,并将用一个通用列表替换它,但决定保持它精益,但主要是出于习惯。 (我们是否应该通过使用泛型来打破习惯并开始新的习惯?)

7 个答案:

答案 0 :(得分:12)

在Delphi XE中,没有理由不使用通用容器。

使用强制转换旧方法将为您提供:

  • 更干净,类型安全,错误更少的代码,
  • 枚举数,用于in循环,
  • 相同的更好的性能特征。

答案 1 :(得分:10)

这是由Deltic的回答引起的,我想提供一个反例,证明你可以使用仿制药进行动物饲养。 (即:多态通用列表)

首先是一些背景:您可以使用通用基类列表类来提供通用动物的原因是因为您通常会有这种继承:

TBaseList = class
  // Some code to actually make this a list
end

TSpecificList = class(TBaseList)
  // Code that reintroduces the Add and GetItem routines to turn TSpecificList
  // into a type-safe list of a different type, compatible with the TBaseList
end

这不适用于泛型,因为您通常会这样:

TDogList = TList<TDog>
end

TCatList = TList<TCat>
end

...两个列表中唯一的“共同祖先”是TObject - 一点儿都没用。但是我们可以定义一个新的通用列表类型,它接受两个类参数:TAnimalTSpecificAnimal,生成一个TSpecificAnimal的类型安全列表,与{{1}的通用列表兼容}}。这是基本的类型定义:

TAnimal

使用这个我们可以做到:

TCompatibleList<T1:class;T2:class> = class(TObjectList<T1>)
private
  function GetItem(i: Integer): T2;
public
  procedure Add(A:T2);
  property Item[i:Integer]:T2 read GetItem;default;
end;

这样TDogList和TCatList实际上都继承自TAnimal = class; TDog = class(TAnimal); TCat = class(TAnimal); TDogList = TCompatibleList<TAnimal, TDog>; TCatList = TCompatibleList<TAnimal, TCat>; ,所以我们现在有一个多态通用列表!

这是一个完整的控制台应用程序,它显示了这个概念的实际应用。该课程现在进入我的ClassLibrary以供将来重用!

TObjectList<TAnimal>

答案 2 :(得分:4)

我们是否应该通过使用泛型来打破习惯并开始新的习惯? 的

答案 3 :(得分:3)

在大多数情况下,是的,通用容器是一件好事。但是,the compiler generates a lot of duplicate code,并且遗憾的是链接器还不知道如何删除它,因此大量使用泛型可能会导致可执行文件膨胀。但除此之外,他们很棒。

答案 4 :(得分:2)

根据Cosmin的回答,本质上是对Deltic答案的回应,这里是如何修复Deltic的代码:

type
  TAnimal = class
  end;

  TDog = class(TAnimal)
  end;

  TAnimalList<T:TAnimal> = class(TList<T>)
    procedure Feed;
  end;
  TDogList = TAnimalList<TDog>;

现在你可以写:

var
  Dogs: TDogList;
...
  Dogs.Feed;

答案 5 :(得分:1)

你写的关于向后兼容性......这是我最关心的问题,如果(像我一样)你正在编写哪些库应该能够用最常见的Delphi版本进行编译。

即使您只将XE用于已关闭的项目,即使您从未发布过代码,也可能会创建自己的自定义库。我们手边都有这些最喜欢的单位,只是不为每个项目重新发明轮子。

在将来的任务中,您可能需要维护一些旧代码,而不可能升级到更新的Delphi版本(1,000,000代码行迁移和审查没有钱)。在这种情况下,您可能会错过仅限XE的库,并使用闪亮的基于通用的列表...

但对于100%“私有”应用程序,如果您确定永远不必维护旧的Delphi代码,我认为没有任何理由不使用泛型。我唯一担心的是重复的代码问题(由Mason引用):CPU缓存可以填充不必要的代码,因此执行速度可能会受到影响。但在实际应用中,我认为你不会看到任何差异。

注意:我刚刚添加了一些new features to my TDynArray wrapper。我试图模仿the sample code from EMB docwiki。所以你可以使用类似通用的功能,使用旧的Delphi版本......当然,泛型更适合使用类,但是对于一些数组和记录,它只是摇滚!

答案 6 :(得分:1)

如果你需要多态列表,那么泛型是一个障碍,而不是一个帮助。例如,这甚至不能编译,因为你不能使用需要TAnimalList的TDogList:

  uses
    Generics.Collections;

  type
    TAnimal = class
    end;

    TDog = class(TAnimal)
    end;

    TAnimalList = TList<TAnimal>;
    TDogList = TList<TDog>;


  procedure FeedTheAnimals(const aList: TAnimalList);
  begin
    // Blah blah blah
  end;


  var
    dogs: TDogList;
  begin
    dogs := TDogList.Create;
    try
      FeedTheAnimals(dogs);

    finally
      dogs.Free;
    end;
  end;

原因很清楚,很容易解释,但同样反直觉。

我自己的观点是,你可以通过使用通用而不是滚动更具体和适合您需求的类型安全容器来节省几秒或几分钟(如果你是一个慢打字员),但你可能最终花费更多的时间来解决泛型在未来的问题和局限性,而不是使用它们开始时保存(并且根据定义,如果到目前为止你还没有使用过通用容器那么你不知道这些问题/局限可能是什么直到遇到它们。)

如果我需要一个TAnimalList,那么我需要或者可以从该列表类中的其他TAnimal特定方法中获益,我希望在TDogList中继承,这反过来可能会引入与其TDog项相关的其他特定成员。

(当然,动物和狗仅用于说明目的。目前我并没有真正研究过兽医代码 - 大声笑)

问题是,你一开始并不总是知道这一点。

防御性编程原则(对我来说,ymmv)为了节省一点时间而将自己画成一个角落很可能最终会花费很多钱。如果没有,那么不采取前期储蓄的额外“成本”本身可以忽略不计。

如果您倾向于如此慷慨,那么您的代码可以与旧版Delphi版本的用户分享更多内容。

:)