代码在Delphi XE 5中正常工作,但不在Delphi 10 Seattle中.
(我无法确定这是否是一个错误或它应该如何工作.尝试搜索embarcadero线索但失败.)
procedure TForm1.Button1Click(Sender: TObject); var FData: TQueue<TBytes>; FsData: TQueue<String>; arr: TBytes; begin FData := TQueue<TBytes>.Create; FsData := TQueue<String>.Create; try setlength(arr,3); arr[0] := 1; arr[1] := 2; arr[2] := 3; FData.Enqueue(arr); Memo1.Lines.Add('Count,array:' + IntToStr(FData.Count)); // 0? FsData.Enqueue('asada'); Memo1.Lines.Add('Count,string:' + IntToStr(FsData.Count)); // 1 finally FData.Free; FsData.Free; end; end;
解决方法
{$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没有条目.因为这两种方法是内联的,所以内置的设备将它压缩成一切.在启动动态数组时不会执行任何操作.
回到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和Seattle的破坏的实现.为了纪录,我放弃了Embarcadero通用集合并使用我自己的类.
这里的背后故事是,在XE8中,Embarcadero决定解决其仿制药的执行缺陷.无论何时实例化通用类型,都会创建所有方法的副本.对于某些方法,为不同的实例生成相同的代码.
因此,对于TGeneric< TFoo> DoSomething和TGeneric< TBar>.具有相同的代码是相当常见的.用于其他语言的其他编译器,C模板,.net泛型等,可以识别这种重复并将相同的通用方法合并在一起. Delphi编译器没有.最终的结果是比一个更严格必要的可执行文件.
在XE8中,Embarcadero决定解决这个问题,我认为这是完全错误的.编译器,而不是攻击问题的根本原因,他们决定改变其通用集合类的实现.如果你看看Generics.Collections中的代码,你会看到它已经被XE8完全重写了.以前来自XE7和更早版本的代码是可读的,从XE8开始,它现在非常复杂和不透明.这个决定有以下后果:
>复杂的代码包含许多错误.其中许多在XE8发布后不久就被发现并被修复.你偶然发现了另一个缺陷.我们学到的一件事是,Embarcadero的内部测试套件不能充分运行他们的收集类.显然,他们的测试是不够的.>通过更改他们的库而不是编译器,他们已经修补了RTL类.通用代码膨胀的原始问题仍然是第三方类.如果Embarcadero在源代码中解决了这个问题,那么他们不仅可以保留XE7中简单而正确的收集类代码,而且所有第三个通用代码都将受益匪浅.