这是 XE8 中引入的一个缺陷。这是我能制作的最简单的复制品。
{$APPTYPE CONSOLE}
uses
System.Generics.Collections;
var
Queue: TQueue<TArray<Byte>>;
begin
Queue := TQueue<TArray<Byte>>.Create;
Queue.Enqueue(nil);
Writeln(Queue.Count);
end.
XE7 的输出为 1,XE8 和西雅图的输出为 0。
这已报告给 Embarcadero:RSP-13196。
Enqueue 的实现如下所示:
procedure TQueue<T>.Enqueue(const Value: T);
begin
if IsManagedType(T) then
if (SizeOf(T) = SizeOf(Pointer)) and (GetTypeKind(T) <> tkRecord) then
FQueueHelper.InternalEnqueueMRef(Value, GetTypeKind(T))
else
FQueueHelper.InternalEnqueueManaged(Value)
else
case SizeOf(T) of
1: FQueueHelper.InternalEnqueue1(Value);
2: FQueueHelper.InternalEnqueue2(Value);
4: FQueueHelper.InternalEnqueue4(Value);
8: FQueueHelper.InternalEnqueue8(Value);
else
FQueueHelper.InternalEnqueueN(Value);
end;
end;
当T 是动态数组时,选择FQueueHelper.InternalEnqueueMRef 分支。这又看起来像这样:
procedure TQueueHelper.InternalEnqueueMRef(const Value; Kind: TTypeKind);
begin
case Kind of
TTypeKind.tkUString: InternalEnqueueString(Value);
TTypeKind.tkInterface: InternalEnqueueInterface(Value);
{$IF not Defined(NEXTGEN)}
TTypeKind.tkLString: InternalEnqueueAnsiString(Value);
TTypeKind.tkWString: InternalEnqueueWideString(Value);
{$ENDIF}
{$IF Defined(AUTOREFCOUNT)}
TTypeKind.tkClass: InternalEnqueueObject(Value);
{$ENDIF}
end;
end;
请注意,TTypeKind.tkDynArray 没有条目。因为这两种方法是内联的,所以内联器设法将它全部压缩为空。 Enqueue 动态数组时不执行任何操作。
回到 XE7 的美好时光,代码如下所示:
procedure TQueue<T>.Enqueue(const Value: T);
begin
if Count = Length(FItems) then
Grow;
FItems[FHead] := Value;
FHead := (FHead + 1) mod Length(FItems);
Inc(FCount);
Notify(Value, cnAdded);
end;
那里没有类型特定缺陷的范围。
我认为您没有简单的解决方法。也许最方便的方法是获取 XE7 TQueue 的代码并使用它来代替 XE8 和西雅图的损坏实现。郑重声明,我已经放弃了 Embarcadero 泛型集合并使用我自己的类。
这里的背景故事是,在 XE8 中,Embarcadero 决定解决其泛型实现中的缺陷。每当您实例化泛型类型时,都会创建所有方法的副本。对于某些方法,为不同的实例生成相同的代码。
所以TGeneric<TFoo>.DoSomething 和TGeneric<TBar>.DoSomething 有相同的代码是很常见的。其他语言的其他编译器、C++ 模板、.net 泛型等,可以识别这种重复并将相同的泛型方法合并在一起。 Delphi 编译器没有。最终结果是一个比严格必要的更大的可执行文件。
在 XE8 中,Embarcadero 决定以我认为完全错误的方式解决这个问题。他们没有攻击问题的根本原因,即编译器,而是决定更改其通用集合类的实现。如果您查看Generics.Collections 中的代码,您会发现它已经完全用XE8 重写。以前 XE7 和更早版本的代码是可读的,而从 XE8 开始,它现在变得非常复杂和不透明。这一决定产生了以下后果:
- 复杂代码包含许多错误。其中许多是在 XE8 发布后不久发现并已修复的。您偶然发现了另一个缺陷。我们了解到的一件事是 Embarcadero 的内部测试套件没有充分运用他们的集合类。很明显,他们的测试不充分。
- 通过更改库而不是编译器,他们修补了 RTL 类。通用代码膨胀的原始问题仍然存在于第三方类。如果 Embarcadero 从源头上解决了这个问题,那么他们不仅可以保留 XE7 中简单而正确的集合类代码,而且所有第三个通用代码都会受益。