【问题标题】:Using Generic containers in Delphi XE - always?在 Delphi XE 中使用通用容器 - 总是?
【发布时间】:2011-07-15 21:16:03
【问题描述】:

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

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

【问题讨论】:

  • 从收到的答案来看,这是 StackOverflow 表现如此出色的一个例子!
  • 使用泛型也会降低构建速度,如下所述:stackoverflow.com/a/24672687/35696

标签: delphi generics delphi-xe


【解决方案1】:

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

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

  • 更简洁、类型安全、不易出错的代码,
  • 枚举器,for in 循环,
  • 相同更好的性能特征。

【讨论】:

  • 不一样的性能特征,更好的性能,因为你不再有固定返回类型的虚拟吸气剂。枚举员是我的“杀手”。我只想说我现在有很多看起来像这样TSomethingList = class(TObjectList<TSomething>) 的类:这些类曾经作为TObjectList 的手写后代存在,但现在变成了泛型容器,所以我可以使用for in 循环。
  • @Cosmin 我不确定平均性能提升是否真实。 Getter 并不那么耗时,并且最好使用局部变量来访问循环中索引项的多个属性,例如而由泛型生成的所有重复代码(参见 Mason 答案)可能会使程序变慢:CPU 缓存将被所有这些不必要的代码填充。
  • 我同意泛型容器可能会提高性能,因为支持泛型的 Delphi 版本具有 TDictionary,如果您需要按键值在列表中查找项目,这可能是您可以获得的最快容器。跨度>
  • @A.Bouchez 有趣的例子,但使用泛型它更简单、更清晰的 IMO。而且您的动态数组使用比字典搜索慢的二进制搜索(如果我调查正确的话)。
  • @darian 您接受的答案中的陷阱实际上是答案作者知识的限制。这不是一个真正的问题。很抱歉你被这个论点所诱惑。
【解决方案2】:

我们应该改掉这个习惯并通过始终使用泛型来开始一个新的习惯吗?

【讨论】:

  • +1 用于具体而简洁地回答所述问题。 :)
  • -1 表示没有回答有关缺点的问题。 ;)
  • @Deltics- 在某种程度上,罗伯特展示了其中一个缺点。
【解决方案3】:

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

【讨论】:

  • 最常见的使用模式不会导致非常臃肿。与新 RTTI 产生的完全不同。
  • @Mason Wheeler - 这只是一个臃肿的 EXE 文件还是速度下降?如果只是 EXE 大小,那不是大问题。
【解决方案4】:

您写了关于向后兼容性的文章...这是我最关心的问题,如果(像我一样)您正在编写应该更好地与最常见的 Delphi 版本编译的库。

即使您只将 XE 用于已关闭的项目,您也可能正在制作自己的一些自定义库,即使您从未发布过代码。我们手头都有这样最喜欢的单元,只是可以不用为每个项目重新发明轮子。

在未来的任务中,您可能必须维护一些旧代码,而无法升级到较新的 Delphi 版本(没有钱进行 1,000,000 行代码迁移和审查)。在这种情况下,您可能会错过仅限 XE 的库,以及基于泛型的闪亮列表...

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

注意:我刚刚添加了一些new features to my TDynArray wrapper。我试图模仿the sample code from EMB docwiki。因此,您可以拥有类似泛型的功能,以及良好的旧 Delphi 版本...当然,泛型更适合处理类,但对于一些数组和记录,它就很糟糕!

【讨论】:

  • +1 我最初说不用担心向后兼容性,但你当然是对的……我必须支持 Delphi 5 应用程序、Delphi 7 应用程序、Delphi 2006 应用程序、Delphi 2007 应用程序和Delphi XE 应用程序...不过,我的任务是将所有内容都转换为 XE。
  • A.Bouchez,您是“特例”,因为您编写库供他人使用,我完全理解您希望使用向后兼容的语言结构。但这不适用于我们其他人:打开Delphi (N+1) 框的那一刻,我们知道我们将使用Delphi (N) 中不可用的功能 - 这就是我们购买Delphi (N+1) 的原因
  • @Cosmin - 为向后兼容编写库是一个“更糟糕的情况”,你是对的。但是有些公司在几个版本的 Delphi 上维护了几个软件。由于技术原因(第三方组件)或业务预算(迁移成本,尤其是到 Delphi Unicode 版本),迁移到较新的 Delphi 并不总是可行的。因此,您可以像 Darian 承认的那样,在同一家公司和您的计算机中仍然存在多个版本的 Delphi。
【解决方案5】:

如果您需要多态列表,那么泛型是一个障碍,而不是帮助。例如,这甚至无法编译,因为您不能在需要 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 项相关的其他特定成员。

(当然,Animal and Dog 仅用于说明目的。我目前实际上并没有编写兽医代码 - LOL)

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

防御性编程原则表明(对我来说,ymmv)为了节省一点时间而把自己逼到角落里可能会在未来付出很多代价。如果不这样做,那么不预先节省的额外“成本”本身就可以忽略不计。

此外,如果您愿意如此慷慨,您的代码更易于与旧版 Delphi 的用户共享。

:)

【讨论】:

  • @Darian:如果您想知道阻止此处示例工作的“清晰但违反直觉”的原则,我不久前写了一篇关于它的文章。 tech.turbu-rpg.com/149/generics-and-the-covariance-problem
  • @Deltics,您的示例是对 Generics.Collections 中默认 TList 的限制,它不是泛型的内在限制。请参阅我的答案,以通过一个例程喂养不同种类的普通动物的示例!
  • -1 通过正确使用约束可以轻松避免这种所谓的陷阱。我很惊讶像你这样有经验的人却不知道这一点。
  • @David,你如何通过“正确使用约束”来解决这个问题?我不认为仅仅有约束就足够了......你仍然有不兼容的继承问题(TList&lt;T&gt; 基本上直接继承自TObject
  • @David - 我还没有学会如何使用新的通用猫剥皮工具来给我需要的猫剥皮。我太忙于使用非常有效且更容易学习和教授的猫剥皮工具来有效、高效和高效地给猫剥皮,以学习多余的知识,或者不得不花时间将多余的知识传授给必须从事相同工作的其他人猫剥皮设施。
【解决方案6】:

这是由 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,生成与TAnimal 的泛型列表兼容的TSpecificAnimal 类型安全列表。这是基本类型定义:

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;

使用这个我们可以做到:

TAnimal = class; 
TDog = class(TAnimal); 
TCat = class(TAnimal);

TDogList = TCompatibleList<TAnimal, TDog>;
TCatList = TCompatibleList<TAnimal, TCat>;

这样 TDogList 和 TCatList 实际上都继承自 TObjectList&lt;TAnimal&gt;,所以我们现在有了一个多态的泛型列表!

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

program Project23;

{$APPTYPE CONSOLE}

uses
  SysUtils, Generics.Collections;

type

  TAnimal = class
  end;

  TDog = class(TAnimal)
  end;

  TCat = class(TAnimal)
  end;

  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;

{ TX<T1, T2> }

procedure TCompatibleList<T1, T2>.Add(A: T2);
begin
  inherited Add(T1(TObject(A)));
end;

function TCompatibleList<T1, T2>.GetItem(i: Integer): T2;
begin
  Result := T2(TObject(inherited Items[i]));
end;

procedure FeedTheAnimals(L: TObjectList<TAnimal>);
var A: TAnimal;
begin
  for A in L do
    Writeln('Feeding a ' + A.ClassName);
end;

var Dogs: TCompatibleList<TAnimal, TDog>;
    Cats: TCompatibleList<TAnimal, TCat>;
    Mixed: TObjectList<TAnimal>;

begin
  try
    // Feed some dogs
    Dogs := TCompatibleList<TAnimal, TDog>.Create;
    try
      Dogs.Add(TDog.Create);
      FeedTheAnimals(Dogs);
    finally Dogs.Free;
    end;
    // Feed some cats
    Cats := TCompatibleList<TAnimal, TCat>.Create;
    try
      Cats.Add(TCat.Create);
      FeedTheAnimals(Cats);
    finally Cats.Free;
    end;
    // Feed a mixed lot
    Mixed := TObjectList<TAnimal>.Create;
    try
      Mixed.Add(TDog.Create);
      Mixed.Add(TCat.Create);
      FeedTheAnimals(Mixed);
    finally Mixed.Free;
    end;
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

【讨论】:

  • 我并没有说泛型是不可能的,我说它们是一种障碍。恭喜:你和你之间的大卫非常有效地证明了我的观点,谢谢! ;) -1 因为它根本不回答问题。这是对另一个答案的回应 - 它应该是带有博客链接或其他扩展媒体的评论,供您观察/解释。
  • @Deltics,我理解你的观点并尊重你投反对票的决定。尽管如此,我还是使用了 79 行代码中的通用多态列表实现了你的整个动物喂养例程,甚至没有试图保持简短。请注意,这是一个完整的控制台应用程序,包括测试。如果他们允许我这样做,仿制药怎么会成为障碍?我决定使用其他“SO Answer”来反驳您的示例符合 SO 实践,因为您不能在 cmets 中有代码。请不要保留我不写博客反对我的决定!
  • @Deltics - 这是对原始问题的详细说明。在我的书中非常好。
  • 赞成,因为这个详细的示例代码在我的工作中对我非常有用。我很感激所花费的时间,并希望抵消毫无根据的反对票!
  • 这正是我想要的。谢谢。
【解决方案7】:

本着 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;

【讨论】:

  • TAnimalList&lt;TDog&gt; 继承自 TList&lt;TDog&gt; 并且继承自 TObject。不能var L:TList&lt;TAnimal&gt; := TAnimalList&lt;TDog&gt;.Create,那不是多态列表。
  • @Cosmin 它解决了 Deltics 的问题。我看过你的多态列表。我并没有试图复制它。
  • @David Deltic 的回答是 "If you need polymoprhic lists then Generics are a hindrance"
  • @Deltics 但是他的例子实际上最好通过我提供的代码来解决,我相信。
  • 一方面,当我需要具有共同祖先的简单对象列表时,我将使用 Cosmin 的解决方案。我通常会。我想忘记使用 TStringLists 作为对象容器的旧时代!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-10-24
  • 2011-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多