Как сделать в TObjectList?

Я пытаюсь использовать for in для повторения TObjectList:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, Contnrs;

var
    list: TObjectlist;
    o: TObject;
begin
    list := TObjectList.Create;
    for o in list do
    begin
        //nothing
    end;
end.

И не компилируется:

[Ошибка dcc32] Project1.dpr(15): E2010 Несовместимые типы: «TObject» и «Pointer»

Создается впечатление, что конструкция for in в Delphi не обрабатывает нетипизированное, неопущенное, TObjectList как перечислимую цель.

Как перечислить объекты в TObjectList?

Что я делаю сейчас

Мой текущий код:

procedure TfrmCustomerLocator.OnBatchDataAvailable(BatchList: TObjectList);
var
   i: Integer;
   o: TObject;
begin
   for i := 0 to BatchList.Count-1 do
   begin
      o := BatchList.Items[i];

      //...snip...where we do something with (o as TCustomer)
   end;
end;    

Без веской причины я надеялся изменить его на:

procedure TfrmCustomerLocator.OnBatchDataAvailable(BatchList: TObjectList);
var
   o: TObject;
begin
   for o in BatchList do
   begin
      //...snip...where we do something with (o as TCustomer)
   end;
end;    

Зачем использовать счетчик? Только причиной.


person Ian Boyd    schedule 29.09.2014    source источник
comment
используйте общий TObjectList<T> вместо TObjectList.   -  person whosrdaddy    schedule 29.09.2014
comment
Пример сложного пути: Поддержка цикла in в потомках TObjectList.   -  person LU RD    schedule 29.09.2014
comment
Я сообщил об этой проблеме как ошибка 10790 в 2005 г. В 2011 г. был отложен до следующего выпуска.   -  person Rob Kennedy    schedule 29.09.2014
comment
в качестве альтернативы вы можете использовать две переменные и абсолютную: var X: Pointer; y:TMyObject абсолютный x;. Теперь вы можете сделать для x в списке do y.doSomeThing();   -  person Ritsaert Hornstra    schedule 30.09.2014


Ответы (3)


Как перечислить объекты в TObjectList?

Чтобы ответить на конкретный вопрос, это пример введения счетчика. Обратите внимание, что вам придется создать потомка TObjectList, чтобы добавить функцию GetEnumerator. Вы могли бы обойтись без создания подклассов с помощью вспомогательного класса, но я оставлю это в качестве упражнения для заинтересованного читателя.

type

TObjectListEnumerator = record
private
  FIndex: Integer;
  FList: TObjectList;
public
  constructor Create(AList: TObjectList);
  function GetCurrent: TObject;
  function MoveNext: Boolean;
  property Current: TObject read GetCurrent;
end;

constructor TObjectListEnumerator.Create(AList: TObjectList);
begin
  FIndex := -1;
  FList := AList;
end;

function TObjectListEnumerator.GetCurrent;
begin
  Result := FList[FIndex];
end;

function TObjectListEnumerator.MoveNext: Boolean;
begin
  Result := FIndex < FList.Count - 1;
  if Result then
    Inc(FIndex);
end;

//-- Your new subclassed TObjectList

Type

TMyObjectList = class(TObjectList)
  public
    function GetEnumerator: TObjectListEnumerator;
end;

function TMyObjectList.GetEnumerator: TObjectListEnumerator;
begin
  Result := TObjectListEnumerator.Create(Self);
end;

Эта реализация перечислителя использует запись вместо класса. Преимущество этого заключается в том, что при выполнении for..in перечислений в куче не выделяется дополнительный объект.

procedure TfrmCustomerLocator.OnBatchDataAvailable(BatchList: TObjectList);
var
   o: TObject;
begin
   for o in TMyObjectList(BatchList) do // A simple cast is enough in this example
   begin
      //...snip...where we do something with (o as TCustomer)
   end;
end;   

Как уже отмечали другие, существует класс дженериков, который лучше использовать, TObjectList<T>.

person LU RD    schedule 29.09.2014
comment
Это отвечает на вопрос. Хотя кажется очевидным, что ответ таков: просто продолжайте использовать целочисленный индекс. Я только пытаюсь использовать for-in для развлечения; это действительно ничего не добавляет. - person Ian Boyd; 30.09.2014

Используя дженерики, вы можете иметь типизированный список объектов (только что заметил комментарий, но все равно закончу это)

И если вы ищете вескую причину для использования TObjectList<T>, эта причина будет заключаться в том, что это сэкономит вам много приведения типов в вашем коде.

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, Generics.Collections;
Type
  TCustomer = class
  private
  public
    //Properties and stuff
  end;

var
    list: TObjectlist<TCustomer>;
    c: TCustomer;
begin
    list := TObjectList<TCustomer>.Create;
    for c in list do
    begin
        //nothing
    end;
end.
person Teun Pronk    schedule 29.09.2014
comment
Вы имеете в виду list := TObjectList<TCustomer>.Create - person David Heffernan; 29.09.2014
comment
К сожалению, используемый API предоставляет TObjectList. - person Ian Boyd; 30.09.2014
comment
@DavidHeffernan, упс, мне плохо. Да, это то, что я имел в виду - person Teun Pronk; 30.09.2014

Перечислитель для TObjectList объявлен в TList, который является классом, производным от которого является TObjectList. RTL не удосуживается объявить более конкретный перечислитель для TObjectList. Итак, у вас остался счетчик TList. Что дает предметы типа Pointer, которые относятся к типу предметов, хранящихся у TList.

Вероятно, есть несколько причин, по которым разработчики RTL решили ничего не делать с TObjectList. Например, я полагаю следующие возможные причины:

  1. TObjectList, как и все в Contnrs, объявлено устаревшим из-за универсальных контейнеров в Generics.Collections. Зачем тратить ресурсы на изменение устаревшего класса.
  2. Даже если бы у TObjectList был счетчик, который выдал TObject элементов, вам все равно пришлось бы их разыгрывать. Будет ли нумератор, выдающий TObject, действительно более полезным?
  3. Разработчики просто забыли о существовании этого класса, когда добавляли перечислители.

Что вы должны сделать. Очевидный выбор — использовать TList<T> или TObjectList<T> из Generics.Collections. Если вы хотите сохранить TObjectList, вы можете подклассировать его и добавить перечислитель, который дал TObject. Если вы не знаете, как это сделать, вы можете узнать, как это сделать, из документация. Или вы можете использовать унаследованный перечислитель и ввести указатели, которые он дает.

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

person David Heffernan    schedule 29.09.2014
comment
Возможно, OP требует использования вместо этого цикла for in или обычного цикла for, потому что ему нужна возможность удалять некоторые объекты в TObjectList в этом цикле. Если это так, то самым простым способом было бы использовать цикл while вместо цикла for. На самом деле цикл while используется в перечислителях. - person SilverWarior; 29.09.2014
comment
@SilverWarior Вы не можете изменять список в цикле for in больше, чем в классическом индексированном цикле for. Ян хочет использовать цикл for in, потому что он более удобочитаем. - person David Heffernan; 29.09.2014
comment
@ Дэвид, относительно аргумента 2: между x := TObject(o) as TMyClass и x := o as TMyClass все еще есть разница, ИМО. - person Rudy Velthuis; 29.09.2014
comment
@RudyVelthuis Да. Я просто предполагаю возможные причины. Только дизайнеры RTL знают ответ на этот вопрос. - person David Heffernan; 29.09.2014
comment
@ Дэвид: А, хорошо. Я думаю, что аргумент 3 наиболее вероятен, так как это уже было состоянием кода задолго до того, как были введены дженерики. - person Rudy Velthuis; 29.09.2014
comment
@DavidHeffernan Если Ян хочет использовать циклы for in только потому, что они кажутся красивыми, то мое предложение против этого. Почему? Поскольку все сравнительные тесты, которые я проводил между производительностью цикла for in и классическим циклом for, у меня всегда была более низкая производительность при использовании циклов for in. А падение их производительности колебалось от 5 и даже до 25 процентов. И это большое влияние на производительность, которое определенно не стоит того, чтобы код выглядел немного лучше. Я провел все тесты на Delphi XE3, поэтому возможно, что последняя версия меньше влияет на производительность. - person SilverWarior; 29.09.2014
comment
@Silver Если производительность не имеет значения, то зачем об этом беспокоиться? Это известно как преждевременная оптимизация. Дрянные разработчики из Emba реализовали свои счетчики как классы, а не как записи, и поэтому платили за выделение кучи. Который стоит. Все мои счетчики построены с помощью записей. - person David Heffernan; 29.09.2014
comment
@DavidHeffernan Не могли бы вы поделиться своим примером кода того, как вы реализуете перечислители с использованием записей? Я бы очень хотел это увидеть. - person SilverWarior; 30.09.2014
comment
Ответ @Silver LURD показывает, как это сделать. Это совсем не сложно. Документация очень понятная. - person David Heffernan; 30.09.2014
comment
Что касается преждевременной оптимизации. Я не думаю, что выбор более быстрого метода, который не требует каких-то других серьезных изменений или накладывает дополнительные ограничения, можно рассматривать как преждевременную оптимизацию. Вы говорите о преждевременной оптимизации, когда выбранная вами оптимизация накладывает на вас определенные ограничения или усложняет дальнейшую разработку вашего кода, чего нельзя сказать о выборе цикла for вместо цикла for in. На самом деле выбор более медленного метода, если он не приносит вам каких-либо других преимуществ, мне кажется просто плохим решением. - person SilverWarior; 30.09.2014
comment
@Silver цикл for in дает явное преимущество в удобочитаемости и ясности по сравнению с индексированным циклом. - person David Heffernan; 30.09.2014
comment
@DavidHeffernan Я не заметил, что LURD предлагает использовать счетчик с использованием записей. Я проверю его более подробно, когда вернусь домой, и проведу сравнительное тестирование, чтобы увидеть, как он работает. - person SilverWarior; 30.09.2014
comment
@DavidHeffernan Лично мне цикл for in кажется не более читаемым, чем индексированный цикл for. Вероятно, это потому, что я использую старые циклы for с самого начала. Еще одна причина, по которой я предпочитаю использовать старые циклы for, заключается в том, что открытый индекс позволяет мне отслеживать достигнутый прогресс. Это нельзя сделать с помощью циклов for in или можно? - person SilverWarior; 30.09.2014
comment
@Silver Индексированный цикл включает в себя дополнительную целочисленную переменную цикла и работу с Count-1. Цикл for oin ясно говорит для каждого элемента в контейнере. Delphi не хватает функции перечисления Python, это правда. Из-за этого я не использую for столько, сколько хотелось бы. - person David Heffernan; 30.09.2014
comment
На самом деле, используя перечислители, которые необходимы для использования for в циклах, у вас все еще есть целое число, которое не подвергается воздействию остальной части кода. И в методе MoveNext вы все еще проверяете, не больше ли целое число, чем количество элементов-1. Основное отличие состоит в том, что внутри цикла for in фактически используется цикл while, который проверяет, возвращает ли метод MoveNext значение True. А в методе MoveNext вы контролируете, как вы повторяете циклы цикла, поэтому вы можете, например, пропустить каждый второй элемент или даже динамически удалить некоторые элементы, с помощью которых вы можете динамически влиять на цикл цикла. - person SilverWarior; 30.09.2014
comment
@Silver Я все это знаю. Весь смысл в том, что переменная цикла скрыта, не так ли? - person David Heffernan; 30.09.2014
comment
@DavidHeffernan Я немного сомневаюсь в этом. Потому что, если это основной смысл циклов for in , то я сомневаюсь, стоило ли тратить все это время на написание счетчиков для всего этого. - person SilverWarior; 30.09.2014
comment
@Silver Тяжело тебе сомневаться в моем личном мнении. Если вы предпочитаете индексированные циклы, это нормально. Если кто-то другой предпочитает циклы for in, разве это не их дело? Независимо от того, разделяете вы это мнение или нет, какие еще возможные причины могут быть для существования циклов for in. В конце концов, компилятор переводит их в цикл while и компилирует. Так что они чистый синтаксический сахар. Но разве не все? Почему мы до сих пор не программируем в машинном коде? - person David Heffernan; 30.09.2014
comment
Я просто хотел высказать свое мнение по этому поводу. И да, иногда я могу быть немного жестким по этому поводу. Но, в конце концов, каждый вправе выбирать, что ему больше нравится. - person SilverWarior; 30.09.2014
comment
Большим преимуществом перечислителей и циклов for-in является то, что они обеспечивают унифицированный интерфейс, независимо от того, индексируема коллекция или нет. Цикл for-in просто перебирает все элементы, проиндексированные или нет. - person Rudy Velthuis; 02.10.2014