Можно хранить массив в TQueue?

Проблема с хранением массива в TQueue. Любая идея, где я ошибаюсь? Код отлично работает в 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;

Ответ 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, но весь третий общий код выиграл бы.