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
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()
. TheGetEnumerator()
method must return a class, interface, or record type.- The class, interface, or record returned by
GetEnumerator()
must contain a public instance method calledMoveNext()
. TheMoveNext()
method must return aBoolean
. 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 calledCurrent
. The type of theCurrent
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
@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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With