Delphi: использование перечислителей для фильтрации TList‹T: class› по типу класса?

Хорошо, это может сбивать с толку. То, что я пытаюсь сделать, это использовать перечислитель, чтобы возвращать только определенные элементы в общем списке на основе типа класса.

Учитывая следующую иерархию:

type
    TShapeClass = class of TShape;

    TShape = class(TObject)
    private
        FId: Integer;
    public
        function ToString: string; override;
        property Id: Integer read FId write FId;
    end;

    TCircle = class(TShape)
    private
        FDiameter: Integer;
    public
        property Diameter: Integer read FDiameter write FDiameter;
    end;

    TSquare = class(TShape)
    private
        FSideLength: Integer;
    public
        property SideLength: Integer read FSideLength write FSideLength;
    end;

    TShapeList = class(TObjectList<TShape>)
    end;

Как я могу расширить TShapeList так, чтобы я мог сделать что-то похожее на следующее:

procedure Foo;
var
    ShapeList: TShapeList;
    Shape: TShape;
    Circle: TCircle;
    Square: TSquare;

begin
    // Create ShapeList and fill with TCircles and TSquares
    for Circle in ShapeList<TCircle> do begin
        // do something with each TCircle in ShapeList
    end;
    for Square in ShapeList<TSquare> do begin
        // do something with each TSquare in ShapeList
    end;
    for Shape in ShapeList<TShape> do begin
        // do something with every object in TShapeList
    end;
end;

Я попытался расширить TShapeList, используя адаптированную версию фрагмента Примоза Габриелчича на параметризованные перечислители с использованием фабричной записи следующим образом:

type
    TShapeList = class(TObjectList<TShape>)
    public
        type
            TShapeFilterEnumerator<T: TShape> = record
            private
                FShapeList: TShapeList;
                FClass: TShapeClass;
                FIndex: Integer;
                function GetCurrent: T;
            public
                constructor Create(ShapeList: TShapeList);
                function MoveNext: Boolean;
                property Current: T read GetCurrent;
            end;

            TShapeFilterFactory<T: TShape> = record
            private
                FShapeList: TShapeList;
            public
                constructor Create(ShapeList: TShapeList);
                function GetEnumerator: TShapeFilterEnumerator<T>;
            end;

        function FilteredEnumerator<T: TShape>: TShapeFilterFactory<T>;
    end;

Затем я изменил Foo так:

procedure Foo;
var
    ShapeList: TShapeList;
    Shape: TShape;
    Circle: TCircle;
    Square: TSquare;

begin
    // Create ShapeList and fill with TCircles and TSquares
    for Circle in ShapeList.FilteredEnumerator<TCircle> do begin
        // do something with each TCircle in ShapeList
    end;
    for Square in ShapeList.FilteredEnumerator<TSquare> do begin
        // do something with each TSquare in ShapeList
    end;
    for Shape in ShapeList.FilteredEnumerator<TShape> do begin
        // do something with every object in TShapeList
    end;
end;

Однако Delphi 2010 выдает ошибку, когда я пытаюсь скомпилировать Foo о Incompatible types: TCircle and TShape. Если я закомментирую цикл TCircle, то получу аналогичную ошибку про TSquare. Если я также закомментирую цикл TSquare, код скомпилируется и заработает. Ну, это работает в том смысле, что он перечисляет все объекты, поскольку все они происходят от TShape. Странно то, что номер строки, который указывает компилятор, находится на 2 строки дальше конца моего файла. В моем демонстрационном проекте он указал строку 177, но там всего 175 строк.

Есть ли способ заставить это работать? Я хотел бы иметь возможность назначать Circle напрямую, не прибегая к каким-либо приведениям типов или проверке самого цикла for.


person afrazier    schedule 29.04.2010    source источник


Ответы (3)


Вы не показываете это здесь, но проблема, вероятно, заключается в реализации GetCurrent.

Пока компилятор принимает что-то вроде

result := FShapeList[FIndex];

в универсальном классе это не удастся, если результирующий класс не равен TShape, как в случае с TCircle и TSquare. Вот почему это работает в третьем цикле.

Измените код на

result := T(FShapeList[FIndex]);

и ты в порядке.

Несуществующий номер строки для ошибки связан с разрешением внутреннего универсального кода, выполненного компилятором, который плохо сопоставляется с номерами строк.

person Uwe Raabe    schedule 30.04.2010
comment
:headdesk: Именно так. Спасибо! - person afrazier; 30.04.2010

Вы не можете сделать это непосредственно в счетчике. Боюсь, вы застряли с "есть". Единственный способ - действительно создать какой-нибудь вспомогательный перечислитель. В вашем случае попробуйте закомментировать строки, пока не найдете единственное, что делает компилятор недовольным.

Извините, это все, что я могу предложить.

person alex    schedule 30.04.2010
comment
Извините, я пытался использовать вспомогательный перечислитель (FilteredEnumerator<T: TShape>), я просто не уточнил это в своем вопросе. Это было обновлено. Есть ли что-то ужасно неправильное, что я делаю в моем помощнике? - person afrazier; 30.04.2010

Вы написали ответ в своем вопросе :)

Вам просто нужно вызвать правильную функцию:

for Circle in ShapeList.FilteredEnumerator<TCircle> do
  //blah blah

for Square in ShapeList.FilteredEnumerator<TSquare> do
  //blah blah

Одно небольшое замечание по поводу вашего кода: вы можете сбросить свою запись TShapeFilterFactory и просто добавить метод:

TShapeFilterEnumerator<T>.GetEnumerator : TShapeFilterEnumerator<T>;
begin
  Result := Self;
end;
person LeGEC    schedule 30.04.2010
comment
На самом деле, я пытался использовать for Circle in ShapeList.FilteredEnumerator<TCircle>. Отсюда и ошибка компилятора. Я обновлю вопрос, чтобы быть более ясным. Вот что я получаю за поспешность моего вопроса. :-) - person afrazier; 30.04.2010
comment
Спасибо за подсказку об удалении заводской записи. - person afrazier; 30.04.2010