На главную
ПРАВИЛА FAQ Помощь Участники Календарь Избранное DigiMania RSS
msm.ru
! Друзья, соблюдайте, пожалуйста, правила форума и данного раздела:
Данный раздел не предназначен для вопросов и обсуждений, он содержит FAQ-заготовки для разных языков программирования. Любой желающий может разместить здесь свою статью. Вопросы же задавайте в тематических разделах!
• Если ваша статья может быть перенесена в FAQ соответствующего раздела, при условии, что она будет оформлена в соответствии с Требованиями к оформлению статей.
• Чтобы остальным было проще понять, указывайте в описании темы (подзаголовке) название языка в [квадратных скобках]!
Модераторы: Модераторы
  
> Перебор записей в TDataSet, [Delphi] Перебор записей без необходимости писать First/Next (Delphi 2006+)
    Перебор записей в TDataSet

    Иногда при переборе записей из датасета хочется написать простой пропуск итерации:
    ExpandedWrap disabled
        ...
        if DS.FieldByName('foo').IsNull then Continue;
        ...

    - ан нет. Необходимость строчки DS.Next держит нас в рамках цикла словно пудовая гиря. Приходится вставлять ветвления, громадные вложенные begin-end и прочее. Я уже не говорю о случае, когда DS.Next просто забываешь поставить.
    Предлагаю пару простеньких оберток, добавляющих синтаксического сахара к перебору записей.

    Способ 1. for-in для датасета (добавляем итератор). Для Дельфи начиная с XE (используются хелперы).

    ExpandedWrap disabled
      {$IF CompilerVersion >= 22} // RAD_XE+
      // реализация энумератора для TDataSet
      type
       
        // заглушка для подставки в условие цикла. Реально нигде не используется
        TDataSetLoop = record
        end;
       
        TDataSetEnum = class
        private
          FOwner: TDataSet;
          FListBegin: Boolean;  // чтобы не пропустить первую запись
          function GetCurrent: TDataSetLoop;
        public
          constructor Create(aOwner: TDataSet);
          function MoveNext: Boolean;
          property Current:  TDataSetLoop read GetCurrent;
        end;
       
        TDataSetEx = class helper for TDataSet
          // возвращает объект-enumerator для конструкции for..in
          function GetEnumerator: TDataSetEnum;
        end;
       
      { TDataSetEnum }
       
      constructor TDataSetEnum.Create(aOwner: TDataSet);
      begin
        inherited Create;
        FOwner := aOwner;
        FListBegin := True;
        FOwner.First;
      end;
       
      function TDataSetEnum.GetCurrent: TDataSetLoop;
      begin
        // ничего не делать
      end;
       
      { Принципы прохода сильно различаются у for-in и по датасету:
        у for-in - while MoveNext do ..., у датасета - do ...; MoveNext; while not Eof
        (причем Eof устанавливается только после MoveNext с последней записи). Поэтому
        для согласования начала цикла вводится флаг FListBegin (для первой итерации не
        двигать указатель записи) и дополнительная проверка на Eof после перехода на
        следующую запись }
      function TDataSetEnum.MoveNext: Boolean;
      begin
        Result := not FOwner.Eof;
        if Result then
          if not FListBegin then
          begin
            FOwner.Next;
            if FOwner.Eof then Result := False;
          end
          else
            FListBegin := False;
      end;
       
      { TDataSetEx }
       
      function TDataSetEx.GetEnumerator: TDataSetEnum;
      begin
        Result := TDataSetEnum.Create(Self);
      end;
       
      {$IFEND}


    Пример использования:
    ExpandedWrap disabled
      var
        loop: TDataSetLoop;
       
        RecCount := 0;
        for loop in qCurr do
        begin
          if qCurr.FieldByName('notnull').IsNull then Break;
          if qCurr.FieldByName('foo').IsNull then Continue;
          if qCurr.FieldByName('bar').IsNull then Continue;
          ...
          Inc(RecCount);
        end;


    + Привычный вид цикла
    + Удобно для больших циклов
    - Надо объявлять переменную

    Способ 2. ForEach для датасета. Для Дельфи начиная с 2006 (используются лямбда-функции). Можно также реализовать в виде хелпера.
    ExpandedWrap disabled
      {$IF CompilerVersion >= 18} // 2006+
      type
        TDataSetForEachProc = reference to procedure (DS: TDataSet; var Continue: Boolean);
       
      procedure DataSetForEach(DS: TDataSet; ForEachProc: TDataSetForEachProc);
      var
        Continue: Boolean;
      begin
        Continue := True;
        DS.First;
        while not DS.Eof do
        begin
          ForEachProc(DS, Continue);
          if not Continue then Break;
          DS.Next;
        end;
      end;
      {$IFEND}


    Пример использования:
    ExpandedWrap disabled
        RecCount := 0;
        DataSetForEach(qCurr, procedure(DS: TDataSet; var Continue: Boolean)
                            begin
                              if qCurr.FieldByName('notnull').IsNull then
                              begin
                                Continue := False;
                                Exit;
                              begin
                              if DS.FieldByName('foo').IsNull then Exit;
                              if DS.FieldByName('bar').IsNull then Exit;
                              ...
                              Inc(RecCount);
                            end);


    + Никаких дополнительных переменных
    - Непривычно для Дельфийского взгляда
    - Не очень удобно для больших циклов


    upd. Исправлен баг в for-in реализации: последняя запись обрабатывалась дважды
    Сообщение отредактировано: Fr0sT -
    Codero ergo sum
    // Программирую — значит, существую
    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
    0 пользователей:


    Рейтинг@Mail.ru
    [ Script Execution time: 0,1001 ]   [ 17 queries used ]   [ Generated: 18.07.18, 04:43 GMT ]