这是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和Seattle中输出为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的简单而正确的集合类代码,而且所有第三个通用代码都会受益。
此外,我们不需要另一个不兼容的字节数组类型。使用'TBytes'。对于“Byte”以外的元素类型,更一般地使用'TArray'。 –
我同意。原始数组是TidBytes(Indy) – Hans
什么不起作用? –