Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi dynamic array iteration and record copying

Tags:

delphi

Does iterating over a dynamic array using for ... in ... do create a copy of the item in the array? For example:

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;

Will item in the loop be a copy of the item in the array or a reference to it?

If it is a copy is it possible to iterate over the array without a copy being created?

Thanks,

AJ

like image 297
AJ. Avatar asked Sep 02 '13 14:09

AJ.


2 Answers

The loop variable of a for/in loop is a copy of the value held by the container over which the loop is iterating.

Since you cannot replace the default enumerator for dynamic arrays, there is no way for you to create an enumerator that returns references rather than copies. If you were to wrap up the array inside a record, you could create an enumerator for the record that would return references.

Obviously you can use a traditional indexed for loop if you wish to avoid making copies.


One might ask, since there appears to be no documentation of the above statement, why the compiler does not choose to implement such for/in loops using references rather than copies. Only the designers can answer that definitely, but I can offer a justification.

Consider a custom enumerator. The documentation describes the mechanism as follows:

To use the for-in loop construct on a class or interface, the class or interface must implement a prescribed collection pattern. A type that implements the collection pattern must have the following attributes:

  • The class or interface must contain a public instance method called GetEnumerator(). The GetEnumerator() method must return a class, interface, or record type.
  • The class, interface, or record returned by GetEnumerator() must contain a public instance method called MoveNext(). The MoveNext() method must return a Boolean. The for-in loop calls this method first to ensure that the container is not empty.
  • The class, interface, or record returned by GetEnumerator() must contain a public instance, read-only property called Current. The type of the Current property must be the type contained in the collection.

The custom enumerator returns each value from the collection via the Current property. And that implies that the value is copied.

So, this means that custom enumerators always use copies. Imagine if the built-in enumerator for arrays could use references. That would result in a significant semantic difference between the two types of enumerators. It's surely plausible that the designers opted for consistency between the semantics of difference types of enumerators

like image 166
David Heffernan Avatar answered Oct 24 '22 17:10

David Heffernan


@David answered that the enumerator is a copy of the record.

He also said that wrapping a dynamic array inside a record would allow for a custom enumerator.

Here is an example of doing that:

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

Just to be complete and following up comments, here is a generic container example for any dynamic array of record, with a custom enumerator.

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.
like image 23
LU RD Avatar answered Oct 24 '22 17:10

LU RD