Implementing List Enumerator OfType in Delphi

前端 未结 2 1920
执念已碎
执念已碎 2020-12-23 21:56

I am using Delphi XE to implement an enumerator that allows filtering the elements of the list by type. I have quickly assembled a test unit as follows:

uni         


        
相关标签:
2条回答
  • 2020-12-23 22:33

    A worked version using system.IEnumerable<T> and system.IEnumerator<T>

    unit uTestList;
    
    interface
    
    uses Generics.Collections;
    
    type
      TListItemBase = class(TObject)
      end; { TListItemBase }
    
      TListItemChild1 = class(TListItemBase)
      end;
    
      TListItemChild2 = class(TListItemBase)
      end;
    
      TTestList<T : TListItemBase> = class;
    
      TOfTypeEnumerator<T : class; TFilter : class> = class(TInterfacedObject, IEnumerator<TFilter>, IEnumerator)
      private
        FTestList : TList<T>;
        FIndex : Integer;
      protected
        constructor Create(Owner : TList<T>); overload;
    
        function GetCurrent: TObject;
        function GenericGetCurrent : TFilter;
        function MoveNext : Boolean;
        procedure Reset;
    
        function IEnumerator<TFilter>.GetCurrent = GenericGetCurrent;
      end;
    
      TOfTypeEnumeratorFactory<T : class; TFilter : class> = class(TInterfacedObject, IEnumerable<TFilter>, IEnumerable)
      private
        FTestList : TList<T>;
      public
        constructor Create(Owner : TList<T>); overload;
        function GetEnumerator : IEnumerator;
        function GenericGetEnumerator : IEnumerator<TFilter>;
        function IEnumerable<TFilter>.GetEnumerator = GenericGetEnumerator;
      end;
    
      TTestList<T : TListItemBase> = class(TList<T>)
      public
        function OfType<TFilter : TListItemBase>() : IEnumerable<TFilter>;
      end; { TTestList }
    
    
    implementation
    
    { TOfTypeEnumerator<T, TFilter> }
    
    constructor TOfTypeEnumerator<T, TFilter>.Create(Owner: TList<T>);
    begin
      inherited Create;
      FTestList := Owner;
      FIndex := -1;
    end;
    
    function TOfTypeEnumerator<T, TFilter>.GenericGetCurrent: TFilter;
    begin
      Result := TFilter(TObject(FTestList[FIndex]));
    end;
    
    function TOfTypeEnumerator<T, TFilter>.GetCurrent: TObject;
    begin
      Result := TObject( FTestList[FIndex] );
    end;
    
    function TOfTypeEnumerator<T, TFilter>.MoveNext: Boolean;
    begin
      repeat
        Inc(FIndex);
      until (FIndex >= FTestList.Count) or FTestList[FIndex].InheritsFrom(TFilter);
      Result := FIndex < FTestList.Count;
    end;
    
    procedure TOfTypeEnumerator<T, TFilter>.Reset;
    begin
      FIndex := -1;
    end;
    
    { TOfTypeEnumeratorFactory<T, TFilter> }
    
    constructor TOfTypeEnumeratorFactory<T, TFilter>.Create(Owner: TList<T>);
    begin
      inherited Create;
      FTestList := Owner;
    end;
    
    function TOfTypeEnumeratorFactory<T, TFilter>.GetEnumerator: IEnumerator;
    begin
      Result := GenericGetEnumerator;
    end;
    
    function TOfTypeEnumeratorFactory<T, TFilter>.GenericGetEnumerator: IEnumerator<TFilter>;
    begin
      Result := TOfTypeEnumerator<T,TFilter>.Create(FTestList);
    end;
    
    { TTestList<T> }
    
    function TTestList<T>.OfType<TFilter>: IEnumerable<TFilter>;
    begin
      Result := TOfTypeEnumeratorFactory<T,TFilter>.Create(self);
    end;
    
    end.
    

    A test procedure:

    var
      MyElem: TListItemBase;
      MyElem1: TListItemChild1;
      MyElem2: TListItemChild2;
    begin
      Memo1.Clear;
      for MyElem in FTestList.OfType<TListItemBase>() do
      begin
        Memo1.Lines.Add('----------');
      end;
      for MyElem1 in FTestList.OfType<TListItemChild1>() do
      begin
        Memo1.Lines.Add('==========');
      end;
      for MyElem2 in FTestList.OfType<TListItemChild2>() do
      begin
        Memo1.Lines.Add('++++++++++');
      end;
    
    0 讨论(0)
  • 2020-12-23 22:49

    You don't want to use the IEnumerator<T> from System.pas, trust me. That thing brings along so much trouble because it inherits from IEnumerator and so has that GetCurrent method with different results (TObject for IEnumerator and T for IEnumerator<T>).

    Better define your own IEnumerator<T>:

    IEnumerator<T> = interface
      function GetCurrent: T;
      function MoveNext: Boolean;
      procedure Reset;
      property Current: T read GetCurrent;
    end;
    

    Same with IEnumerable. I would say define your own IEnumerable<T>:

    IEnumerable<T> = interface
      function GetEnumerator: IEnumerator<T>;
    end;
    

    If you use that in your TOfTypeEnumerator<T, TFilter> you can remove the method resolution clauses causing the ICE.

    When you do that you will start seeing other compiler errors E2008, E2089 and some more.

    • calling just inherited in your constructor tries to call the constructor with the same signature in your ancestor class which does not exist. So change it to inherited Create.

    • don't use IEnumerable but use IEnumerable<TFilter> because that is what your want to enumerator over

    • don't use methods and casts that are only allowed for objects or specify the class constraint on T and TFilter

    • MoveNext needs a Result

    Here is the compiling unit. Did a quick test and it seems to work:

    unit uTestList;
    
    interface
    
    uses
      Generics.Collections;
    
    type
      IEnumerator<T> = interface
        function GetCurrent: T;
        function MoveNext: Boolean;
        property Current: T read GetCurrent;
      end;
    
      IEnumerable<T> = interface
        function GetEnumerator: IEnumerator<T>;
      end;
    
      TOfTypeEnumerator<T: class; TFilter: class> = class(TInterfacedObject, IEnumerator<TFilter>)
      private
        FTestList: TList<T>;
        FIndex: Integer;
      protected
        constructor Create(Owner: TList<T>); overload;
    
        function GetCurrent: TFilter;
        function MoveNext: Boolean;
      end;
    
      TOfTypeEnumeratorFactory<T: class; TFilter: class> = class(TInterfacedObject, IEnumerable<TFilter>)
      private
        FTestList: TList<T>;
      public
        constructor Create(Owner: TList<T>); overload;
        function GetEnumerator: IEnumerator<TFilter>;
      end;
    
      TTestList<T: class> = class(TList<T>)
      public
        function OfType<TFilter: class>: IEnumerable<TFilter>;
      end;
    
    implementation
    
    { TOfTypeEnumerator<T, TFilter> }
    
    constructor TOfTypeEnumerator<T, TFilter>.Create(Owner: TList<T>);
    begin
      inherited Create;
      FTestList := Owner;
      FIndex := -1;
    end;
    
    function TOfTypeEnumerator<T, TFilter>.GetCurrent: TFilter;
    begin
      Result := TFilter(TObject(FTestList[FIndex]));
    end;
    
    function TOfTypeEnumerator<T, TFilter>.MoveNext: Boolean;
    begin
      repeat
        Inc(FIndex);
      until (FIndex >= FTestList.Count) or FTestList[FIndex].InheritsFrom(TFilter);
      Result := FIndex < FTestList.Count;
    end;
    
    { TOfTypeEnumeratorFactory<T, TFilter> }
    
    constructor TOfTypeEnumeratorFactory<T, TFilter>.Create(Owner: TList<T>);
    begin
      inherited Create;
      FTestList := Owner;
    end;
    
    function TOfTypeEnumeratorFactory<T, TFilter>.GetEnumerator: IEnumerator<TFilter>;
    begin
      Result := TOfTypeEnumerator<T, TFilter>.Create(FTestList);
    end;
    
    { TTestList<T> }
    
    function TTestList<T>.OfType<TFilter>: IEnumerable<TFilter>;
    begin
      Result := TOfTypeEnumeratorFactory<T,TFilter>.Create(self);
    end;
    
    end.
    
    0 讨论(0)
提交回复
热议问题