Jest to wada wprowadzony w XE8. Oto najprostsza reprodukcja, którą mogę wyprodukować.
{$APPTYPE CONSOLE}
uses
System.Generics.Collections;
var
Queue: TQueue<TArray<Byte>>;
begin
Queue := TQueue<TArray<Byte>>.Create;
Queue.Enqueue(nil);
Writeln(Queue.Count);
end.
Wyjście to 1 w XE7 i 0 w XE8 i Seattle.
Zostało to już zgłoszone do Embarcadero: RSP-13196.
Realizacja Enqueue
wygląda następująco:
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;
Kiedy T
to dynamiczna tablica The FQueueHelper.InternalEnqueueMRef
oddział jest wybrany. To z kolei wygląda następująco:
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;
Zauważ, że nie ma wpisu dla TTypeKind.tkDynArray
. Ponieważ te dwie metody są wbudowane, inlinerowi udaje się skompresować wszystko do zera. Nie wykonujesz żadnej akcji, gdy jesteś dynamiczną tablicą.
Powrót do starych, dobrych czasów XE7 kod wyglądał następująco:
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;
No zakres wad specyficzny rodzaj tam.
Nie sądzę, że istnieje łatwy sposób obejścia problemu. Być może najbardziej sensownym sposobem postępowania jest pobranie kodu XE7 TQueue
i użycie go zamiast uszkodzonej implementacji z XE8 i Seattle. Dla przypomnienia zrezygnowałem z generycznych kolekcji Embarcadero i wykorzystałem własne klasy.
Opowieść o tym, że w XE8, Embarcadero zdecydowało się rozwiązać problem niedoboru w ich stosowaniu leków generycznych. Za każdym razem, gdy tworzysz typ ogólny, tworzone są kopie wszystkich metod. W przypadku niektórych metod generowany jest identyczny kod dla różnych wystąpień.
Jest więc dość powszechne, aby TGeneric<TFoo>.DoSomething
i TGeneric<TBar>.DoSomething
miały identyczny kod. Inne kompilatory dla innych języków, szablony C++, .NET generics itp. Rozpoznają to duplikowanie i łączą ze sobą identyczne generyczne metody. Kompilator Delphi tego nie robi. Końcowym rezultatem jest większy plik wykonywalny, niż jest to absolutnie konieczne.
W XE8 Embarcadero zdecydował się rozwiązać ten problem w sposób, który uważam za całkowicie niewłaściwy. Zamiast atakować główną przyczynę problemu, kompilator, postanowili zmienić implementację swoich ogólnych klas kolekcji. Jeśli spojrzysz na kod w Generics.Collections
, zobaczysz, że został całkowicie przepisany w XE8. Tam, gdzie wcześniej kod z XE7 i wcześniejszych był czytelny, od XE8 jest teraz niezwykle złożony i nieprzejrzysty.Decyzja ta miała następujące konsekwencje:
- Kod złożony zawierał wiele błędów. Wiele z nich znaleziono wkrótce po wydaniu XE8 i naprawiono. Natknąłeś się na kolejną wadę. Jedną z rzeczy, których się nauczyliśmy, jest to, że wewnętrzny zestaw testów Embarcadero nie wykonuje wystarczająco swoich klas zbierania danych. Oczywiste jest, że ich testy są niewystarczające.
- Zmieniając bibliotekę, a nie kompilator, poprawili klasy RTL. Pierwotny problem z ogólnym nadpisaniem kodu pozostaje dla klas stron trzecich. Gdyby Embarcadero naprawił problem u źródła, to nie tylko mogliby zachować prosty i prawidłowy kod kolekcji z XE7, ale skorzystałby z tego cały trzeci kod generyczny.
Poza tym nie potrzebujemy kolejnego niekompatybilnego typu tablicy bajtowej. Użyj 'TBytes'. Bardziej ogólnie użyj 'TArray' dla typów elementów innych niż 'Byte'. –
Zgadzam się. Tablica początkowa to TidBytes (Indy) – Hans
Co nie działa? –