How to properly make an interface support iteration?

后端 未结 2 1058
逝去的感伤
逝去的感伤 2021-01-06 01:53

How can I expose this TList from an interface, as either IEnumerator or IEnumerator? I am using Delphi XE.

Here\'s h

2条回答
  •  囚心锁ツ
    2021-01-06 02:08

    If you really want to make a class that implements IEnumerable, you can do it like this:

    unit uGenericEnumerable;
    
    interface
    
    uses SysUtils, Classes, Generics.Collections;
    
    type TGenericEnumerator = class(TInterfacedObject, IEnumerator, IEnumerator)
      private
        FList: TList;
        FIndex: Integer;
      protected
        function GenericGetCurrent: T;
      public
        constructor Create(AList: TList);
        procedure Reset;
        function MoveNext: Boolean;
        function GetCurrent: TObject;
        function IEnumerator.GetCurrent = GenericGetCurrent;
    
        property Current: T read GenericGetCurrent;
    end;
    
    type TNonGenericEnumerable = class(TInterfacedObject, IEnumerable)
      protected
        function GetNonGenericEnumerator: IEnumerator; virtual; abstract;
      public
        function IEnumerable.GetEnumerator = GetNonGenericEnumerator;
    end;
    
    type TGenericEnumerable = class(TNonGenericEnumerable, IEnumerable)
      private
        FList: TList;
      public
        constructor Create;
        destructor Destroy; override;
        function GetNonGenericEnumerator: IEnumerator; override;
        function GetEnumerator: IEnumerator;
        property List: TList read FList;
    end;
    
    implementation
    
    { TGenericEnumerator }
    
    constructor TGenericEnumerator.Create(AList: TList);
    begin
      inherited Create;
      FList := AList;
      FIndex := -1;
    end;
    
    procedure TGenericEnumerator.Reset;
    begin
      FIndex := -1;
    end;
    
    function TGenericEnumerator.MoveNext: Boolean;
    begin
      if FIndex < FList.Count then
      begin
        Inc(FIndex);
        Result := FIndex < FList.Count;
      end
      else
      begin
        Result := False;
      end;
    end;
    
    function TGenericEnumerator.GenericGetCurrent: T;
    begin
      Result := FList[FIndex];
    end;
    
    function TGenericEnumerator.GetCurrent: TObject;
    begin
      // If T has not been constrained to being a class, raise an exception instead of trying to return an object.
      raise Exception.Create('Cannot use this as a non-generic enumerator');
      // If T has been constrained to being a class, return GenericGetCurrent.
      // Result := GenericGetCurrent;
    end;
    
    
    { TGenericEnumerable }
    
    constructor TGenericEnumerable.Create;
    begin
      inherited Create;
      FList := TList.Create;
    end;
    
    destructor TGenericEnumerable.Destroy;
    begin
      FList.Free;
    end;
    
    function TGenericEnumerable.GetEnumerator: IEnumerator;
    begin
      Result := TGenericEnumerator.Create(FList);
    end;
    
    function TGenericEnumerable.GetNonGenericEnumerator: IEnumerator;
    begin
      Result := GetEnumerator;
    end;
    
    end.
    

    Now, your FungibleTrollUnit will look something like this:

    unit FungibleTrollUnit;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics,
      Controls, Forms, Generics.Collections,
      uGenericEnumerable;
    
    type
      IFungibleTroll = interface
        ['{03536137-E3F7-4F9B-B1F5-2C8010A4D019}']
           function GetTrollName:String;
           function GetTrollRetailPrice:Double;
      end;
    
      IFungibleTrolls = interface(IEnumerable)
        ['{090B45FB-2925-4BFC-AE97-5D3F54E1C575}']
          function GetTrolls: TList;
          function FindSingleItemByName(aName:String):IFungibleTroll;
          function FindMultipleItemsByName(aName:String):IEnumerable;
    
          property Trolls:TList read GetTrolls;
      end;
    
    
      TFungibleTrolls = class (TGenericEnumerable, IFungibleTrolls, IEnumerable)
          public
             function GetTrolls: TList;
    
             function FindSingleItemByName(aName: String): IFungibleTroll;
             function FindMultipleItemsByName(aName: String): IEnumerable;
    
             property Trolls:TList read GetTrolls;
      private
      end;
    
    implementation
    
    uses StrUtils;
    
    { TFungibleTrolls }
    
    function TFungibleTrolls.GetTrolls: TList;
    begin
      Result := List;
    end;
    
    function TFungibleTrolls.FindMultipleItemsByName(aName: String): IEnumerable;
      var FilteredTrolls: TGenericEnumerable;
      var Troll: IFungibleTroll;
    begin
      FilteredTrolls := TGenericEnumerable.Create;
      for Troll in List do
      begin
        if Troll.GetTrollName = aName then
          FilteredTrolls.List.Add(Troll);
      end;
      Result := IEnumerable(FilteredTrolls);
    end;
    
    function TFungibleTrolls.FindSingleItemByName(aName: String): IFungibleTroll;
      var Troll: IFungibleTroll;
    begin
      Result := nil;
      for Troll in List do
      begin
        if Troll.GetTrollName = aName then
          Result := Troll;
          break;
      end;
    end;
    
    end.
    

    Note that the implementation of IEnumerable does not work, but IEnumerable does work.
    This is because, unless T is constrained, you cannot convert a T to a TObject.
    If T is a string or an integer, for example, then the IEnumerator does not have a TObject to return.

    If T is constrained to be a class, you can get the implementation of IEnumerable to work.
    If you constrain T to be an IInterface, you could get IEnumerable to work (Delphi 2010 and after (cast GenericGetCurrent to an IInterface, then to a TObject)), but I doubt if that is an advantage.

    I would rather use it without the constraints, and do without being able to iterate everything as TObjects.

    TGenericEnumerable.GetEnumerator can't use FList.GetEnumerator because TList.GetEnumerator does not return an IEnumerator

    Even though you can implement TGenericEnumerable without defining TNonGenericEnumerable, like this:

    type TGenericEnumerable = class(TInterfacedObject, IEnumerable, IEnumerable)
      private
        FList: TList;
      protected
        function GenericGetEnumerator: IEnumerator;
      public
        constructor Create;
        destructor Destroy; override;
        function GetEnumerator: IEnumerator;
        function IEnumerable.GetEnumerator = GenericGetEnumerator;
    
        property List: TList read FList;
    end;
    

    the disadvantage of doing this is that if you try to iterate using the TGenericEnumerable object, rather than the interface, GetEnumerator will be non-generic and you can only iterate TObjects.

    Usual caveats about mixing references to an interface and its underlying object. If you refer to an object both as an object type and as an IEnumerable<....>, then when the interface reference count goes back to zero, the object will be freed even if you still have a reference to it as an object. (That's why I defined IFungibleTrolls; so that I could refer to the collection as an interface).

    You can make alternative implementations of TGenericEnumerator; for example, it could contain a reference to a list, an index and a selection predicate, which are all supplied in the constructor.

提交回复
热议问题