Иерархия динамических массивов 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.