Иерархия динамических массивов Delphi и копирование записи

Выполняет ли итерация по динамическому массиву с помощью for ... in ... do создать копию элемента в массиве? Например:

type
  TSomeRecord =record
    SomeField1 :string;
    SomeField2 :string;
  end;

var
  list: array of TSomeRecord;
  item: TSomeRecord;

begin
  // Fill array here
  for item in list do
    begin
      // Is item here a copy of the item in the array or a reference to it?
    end;
end;

Будет ли элемент в цикле быть копией элемента в массиве или ссылкой на него?

Если это копия, можно ли перебирать массив без создания копии?

Спасибо,

AJ

Ответ 1

Переменная цикла для цикла for/in представляет собой копию значения, хранящегося в контейнере, в котором цикл повторяется.

Поскольку вы не можете заменить перечислитель по умолчанию для динамических массивов, вы не сможете создать перечислитель, который возвращает ссылки, а не копии. Если вы закроете массив внутри записи, вы можете создать перечислитель для записи, которая вернет ссылки.

Очевидно, вы можете использовать традиционный индексированный цикл, если хотите избежать копирования.


Можно спросить, поскольку, как представляется, нет документации по вышеуказанному утверждению, почему компилятор не выбирает реализацию таких циклов for/in, используя ссылки, а не копии. Только дизайнеры могут ответить на это определенно, но я могу предложить оправдание.

Рассмотрим пользовательский перечислитель. Документация описывает механизм следующим образом:

Чтобы использовать конструкцию цикла for-in для класса или интерфейса, класс или интерфейс должны реализовать предписанный шаблон сбора. тип, который реализует шаблон коллекции, должен иметь следующее атрибуты:

  • Класс или интерфейс должен содержать метод открытого экземпляра с именем GetEnumerator(). Метод GetEnumerator() должен возвращать класс, интерфейса или типа записи.
  • Класс, интерфейс или запись, возвращаемые GetEnumerator(), должны содержать метод открытого экземпляра с именем MoveNext(). MoveNext()метод должен возвращать значение Boolean. Цикл for-in вызывает этот метод сначала убедитесь, что контейнер не пуст.
  • Класс, интерфейс или запись, возвращаемые GetEnumerator(), должны содержать открытый экземпляр, свойство только для чтения, называемое Current. тип свойства Current должен быть типом, содержащимся в коллекция.

Пользовательский перечислитель возвращает каждое значение из коллекции с помощью свойства Current. И это означает, что значение копируется.

Итак, это означает, что пользовательские счетчики всегда используют копии. Представьте себе, может ли встроенный перечислитель для массивов использовать ссылки. Это приведет к значительной семантической разнице между двумя типами счетчиков. Несомненно, что дизайнеры выбрали согласованность между семантикой разностных типов счетчиков

Ответ 2

@David ответил, что перечислитель является копией записи.

Он также сказал, что обертка динамического массива внутри записи позволит создать пользовательский перечислитель.

Вот пример этого:

program ProjectCustomEnumerator;

{$APPTYPE CONSOLE}

type
  PSomeRecord = ^TSomeRecord;
  TSomeRecord = record
    SomeField1 :string;
    SomeField2 :string;
  end;

  TSomeRecordArray = record
  private type
    TSomeRecordDynArray = array of TSomeRecord;
    // For x in .. enumerator
    TSomeRecordArrayEnumerator = record
        procedure Create( const AnArray : TSomeRecordDynArray);
      private
        FCurrent,FLast : Integer;
        FArray : TSomeRecordDynArray;
        function GetCurrent : PSomeRecord; inline;
      public
        function MoveNext : Boolean; inline;
        property Current : PSomeRecord read GetCurrent;
    end;
  public
    List : TSomeRecordDynArray;
    // Enumerator interface
    function GetEnumerator : TSomeRecordArrayEnumerator; inline;
  end;

procedure TSomeRecordArray.TSomeRecordArrayEnumerator.Create(
  const AnArray: TSomeRecordDynArray);
begin
  FCurrent := -1;
  FLast := Length(AnArray)-1;
  FArray := AnArray;
end;

function TSomeRecordArray.TSomeRecordArrayEnumerator.GetCurrent: PSomeRecord;
begin
  Result := @FArray[FCurrent];
end;

function TSomeRecordArray.TSomeRecordArrayEnumerator.MoveNext: Boolean;
begin
  Inc(FCurrent);
  Result := (FCurrent <= FLast);
end;

function TSomeRecordArray.GetEnumerator: TSomeRecordArrayEnumerator;
begin
  Result.Create(Self.List);
end;

var
  aList : TSomeRecordArray;
  item : PSomeRecord;
  i    : Integer;
begin
  // Fill array here
  SetLength(aList.List,2);
  aList.List[0].SomeField1 := 'Ix=0; Field1';
  aList.List[0].SomeField2 := 'Ix=0; Field2';
  aList.List[1].SomeField1 := 'Ix=1; Field1';
  aList.List[1].SomeField2 := 'Ix=1; Field2';
  i := -1;
  for item in aList do
  begin
    // Item here a pointer to the item in the array
    Inc(i);
    WriteLn('aList index:',i,' Field1:',item^.SomeField1,' Field2:',item^.SomeField2);
  end;
  ReadLn;
end.

Edit

Чтобы быть полным и следить за комментариями, здесь приведен общий пример контейнера для любого динамического массива записи с пользовательским перечислителем.

program ProjectCustomEnumerator;

{$APPTYPE CONSOLE}

type
  PSomeRecord = ^TSomeRecord;
  TSomeRecord = record
    SomeField1 :string;
    SomeField2 :string;
  end;

  TRecordArray<T> = record
  private type
    TRecordDynArray = array of T;
    // For x in .. enumerator
    TRecordArrayEnumerator = record
        procedure Initialize( const AnArray : TRecordDynArray);
      private
        FCurrent,FLast : Integer;
        FArray : TRecordDynArray;
        function GetCurrent : Pointer; inline;
      public
        function MoveNext : Boolean; inline;
        property Current : Pointer read GetCurrent;
    end;
  public
    List : TRecordDynArray;
    // Enumerator interface
    function GetEnumerator : TRecordArrayEnumerator; inline;
  end;

procedure TRecordArray<T>.TRecordArrayEnumerator.Initialize(
  const AnArray: TRecordDynArray);
begin
  FCurrent := -1;
  FLast := Length(AnArray)-1;
  FArray := AnArray;
end;

function TRecordArray<T>.TRecordArrayEnumerator.GetCurrent: Pointer;
begin
  Result := @FArray[FCurrent];
end;

function TRecordArray<T>.TRecordArrayEnumerator.MoveNext: Boolean;
begin
  Inc(FCurrent);
  Result := (FCurrent <= FLast);
end;

function TRecordArray<T>.GetEnumerator: TRecordArrayEnumerator;
begin
  Result.Initialize(Self.List);
end;

var
  aList : TRecordArray<TSomeRecord>;
  item : PSomeRecord;
  i    : Integer;
begin
  // Fill array here
  SetLength(aList.List,2);
  aList.List[0].SomeField1 := 'Ix=0; Field1';
  aList.List[0].SomeField2 := 'Ix=0; Field2';
  aList.List[1].SomeField1 := 'Ix=1; Field1';
  aList.List[1].SomeField2 := 'Ix=1; Field2';
  i := -1;
  for item in aList do
  begin
    // Item here a pointer to the item in the array
    Inc(i);
    WriteLn('aList index:',i,' Field1:',item^.SomeField1,' Field2:',item^.SomeField2);
  end;
  ReadLn;
end.