当需要一个项目和该项目的一种强类型列表时,通用容器可以节省时间。它可以避免重复编码,例如创建一个带有TList内部变量、类型化的Add/Delete类型方法以及其他优点(例如由通用容器类提供的所有新功能)。
然而,推荐在以后的代码中始终使用通用容器来创建强类型列表吗? 这样做的具体缺点是什么? (如果不担心代码向后兼容性。)昨天我正在编写一个服务器应用程序,我有一个项目列表,最初打算用通用列表替代,但出于习惯还是保留了它。(我们是否应该打破这个习惯,通过始终使用通用容器来开始一个新习惯?)
当需要一个项目和该项目的一种强类型列表时,通用容器可以节省时间。它可以避免重复编码,例如创建一个带有TList内部变量、类型化的Add/Delete类型方法以及其他优点(例如由通用容器类提供的所有新功能)。
然而,推荐在以后的代码中始终使用通用容器来创建强类型列表吗? 这样做的具体缺点是什么? (如果不担心代码向后兼容性。)昨天我正在编写一个服务器应用程序,我有一个项目列表,最初打算用通用列表替代,但出于习惯还是保留了它。(我们是否应该打破这个习惯,通过始终使用通用容器来开始一个新习惯?)
在 Delphi XE 中,没有理由不使用泛型容器。
与旧的强制类型转换方法相比,使用泛型容器会给您带来:
TSomethingList = class(TObjectList<TSomething>)
:这些类曾经作为手写的TObjectList
后代存在,但现在已转变为泛型容器,以便我可以使用for in
循环。 - Cosmin Prund这是由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
- 没有任何帮助。但是我们可以定义一个新的通用列表类型,它接受两个类参数:一个TAnimal
和一个TSpecificAnimal
,生成与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<TAnimal>
,因此我们现在拥有了一个多态的通用列表!
下面是一个完整的控制台应用程序,展示了这个概念的实现。而且这个类现在将被放入我的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.
我们是否应该打破惯例,始终使用泛型? 是的
在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;
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,那么我需要或者可以从该列表类中受益,因为我想要继承TDogList中的其他TAnimal特定方法,这反过来可能会引入与其TDog项目相关的其他特定成员。
(当然,动物和狗仅用于说明目的。我目前并不从事兽医代码工作-哈哈)
问题是,你并不总是在开始时就知道这一点。
防御性编程原则建议(对我而言,可能因人而异)为了节省一点时间而将自己陷入困境,最终可能会付出很大的代价。即使没有这种情况发生,不采取前期节约的额外“成本”本身也微不足道。
此外,如果您愿意慷慨一些,您的代码可以与使用旧版Delphi的用户共享。
:)
TList<T>
本质上直接从 TObject
继承)。 - Cosmin Prund你提到了向后兼容性...这是我最大的关注点,如果(像我一样)你正在编写应该与大多数常见版本的Delphi编译更好的库。
即使你只是在一个封闭项目中使用XE,你可能也会制作一些自己的自定义库,即使你从未发布过代码。我们都有这样的喜爱单元可用,只需在每个项目中重新发明轮子。
在将来的任务中,您可能需要维护一些旧代码,无法升级到新版本的Delphi(没有资金进行100万行代码迁移和审查)。在这种情况下,您可能会错过您的XE-only库,带有闪亮的基于泛型的列表...
但对于一个100%“私人”应用程序,如果您确定永远不需要维护旧的Delphi代码,我认为没有理由不使用泛型。我的唯一担忧是重复的代码问题(如Mason所引用的):CPU缓存可能会填充不必要的代码,因此执行速度可能会受到影响。但在实际应用程序中,我认为您不会看到任何区别。
注意:我刚刚为我的TDynArray包装器添加了一些新功能。我试图模仿EMB docwiki的示例代码。因此,您可以在旧版本的Delphi中拥有类似泛型的功能...当然,泛型更适合处理类,但对于某些数组和记录,它真的很棒!
Delphi (N+1)
盒子时,我们知道我们将使用 Delphi (N)
中不可用的功能 - 这就是为什么我们购买了 Delphi (N+1)
的原因。 - Cosmin Prund