Ответ 1
Это дефект, введенный в 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.
Выход 1 в XE7 и 0 в XE8 и Сиэтле.
Об этом уже сообщалось 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
нет записи. Поскольку эти два метода встроены, inliner удается сжать все до нуля. Никакое действие не выполняется, если 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 и Seattle. Для записи я отказался от общих коллекций Embarcadero и использовал свои собственные классы.
История назад здесь заключается в том, что в XE8 Embarcadero решил устранить недостаток в их реализации дженериков. Всякий раз, когда вы создаете типичный тип, создаются копии всех методов. Для некоторых методов идентичный код генерируется для разных экземпляров.
Таким образом, для TGeneric<TFoo>.DoSomething
и TGeneric<TBar>.DoSomething
достаточно иметь одинаковый код. Другие компиляторы для других языков, шаблоны С++,.net generics и т.д., Распознают это дублирование и объединяют идентичные общие методы. Компилятор Delphi этого не делает. Конечный результат - это более сложный исполняемый файл, чем это необходимо.
В XE8 Эмбаркадеро решил заняться этим в том, что я считаю совершенно неправильным. Вместо того, чтобы атаковать первопричину проблемы, компилятор решил изменить реализацию своих общих классов коллекций. Если вы посмотрите на код в Generics.Collections
, вы увидите, что он полностью переписан в XE8. Если ранее код из XE7 и ранее был доступен для чтения, то из XE8 он теперь чрезвычайно сложный и непрозрачный. Это решение имело следующие последствия:
- Сложный код содержит много ошибок. Многие из них были обнаружены вскоре после того, как XE8 был выпущен и исправлен. Вы наткнулись на другой недостаток. Одна вещь, которую мы узнали, заключается в том, что внутренний набор тестов Embarcadero недостаточно реализует свои классы коллекций. Очевидно, что их тесты неадекватны.
- Изменяя свою библиотеку, а не компилятор, они закрепили классы RTL. Оригинальная проблема с родовым раздуванием кода остается для сторонних классов. Если бы Embarcadero исправил проблему у источника, то не только они могли бы сохранить простой и правильный код класса коллекции из XE7, но весь третий общий код выиграл бы.