Delphi: Использование перечислителей для фильтрации TList <T: class> по типу класса? - PullRequest
1 голос
/ 30 апреля 2010

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

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

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.

Ответы [ 3 ]

3 голосов
/ 30 апреля 2010

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

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

result := FShapeList[FIndex];

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

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

result := T(FShapeList[FIndex]);

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

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

0 голосов
/ 30 апреля 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;
0 голосов
/ 30 апреля 2010

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

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

...