开发者

Enumerating a custom array so I can use for-in

开发者 https://www.devze.com 2023-01-23 05:52 出处:网络
I knew how to do this but forgot again... Quite irritating, because I\'m working on a class that contains a list of XML files, and now I just want to use a for-in loop to walk through all files in thi

I knew how to do this but forgot again... Quite irritating, because I'm working on a class that contains a list of XML files, and now I just want to use a for-in loop to walk through all files in this list. This is the class I have right now:

type
  TXmlFileList = class( TInterfacedObject )
  private
    type
      TItem = class( TInterfacedObject )
      strict private
        FCaption: string;
      protected
        constructor Create( const ACaption: string; const AXML: WideString );
      public
        destructor Destroy; override;
        property Caption: string read FCaption;
      end;
  strict private
    FXmlFiles: array of TXmlFileList.TItem;
  strict protected
    function GetXmlFile( index: Integer ): TXmlFileList.TItem;
  public
    constructor Create( );
    destructor Destroy; override;
    function Add( const ACaption: string; const AXML: WideString ): Integer; overload;
    function Add( const AFilename: string ): Integer; overload;
    function Count: Integer;
    procedure Clear;
    property XmlFile[ index: Integer ]: TXmlFileList.TItem read GetXmlFile; default;
  end;

Looks funny? :-) I know, but I want to hide the definition of the TXmlFile class to the outside world. Basically, the TXmlFileList class allows me to simply refer to XmlFileList[I] to get the file at position I. Works nicely.

But now I want to loop through the TXmlFileList.TItem elements, so I have to expose the TXmlFileList.TItem class. It's not enough, though. It needs an enumerator too in the TXmlFileList class!

How to create that enumerator?

You're probably wondering why I use this complex construction. Well, it might be complex but it will be used by some other developers and I don't 开发者_StackOverflow社区want to provide more methods than they need. This way, I only give them the methods "Add", "Clear" and "Count" to loop through the list, and any property defined on the TItem itself. They don't need more than this, although I might add a few more features later on...


Found it! I needed to create a new class, which I've called TItemEnumerator, and I also included it in the TXmlFileList class, right after TItem:

    type
      TItemEnumerator = class( TObject )
      strict private
        FOwner: TXmlFileList;
        FIndex: Integer;
      protected
        constructor Create(AOwner: TXmlFileList);
      public
        function GetCurrent: TItem;
        function MoveNext: Boolean;
        property Current: TItem read GetCurrent;
      end;

Implementation was easy, just an additional method to TXmlFileList:

    function GetEnumerator: TItemEnumerator;

And finally, exposing the class to the outside world, which I did by adding this to TXmlFileList:

type TXmlFile = TXmlFileList.TItem;

Yeah, that dirty! :-)


It results in this code:

type
  TXmlFileList = class( TInterfacedObject )
  private
    type
      TItem = class( TInterfacedObject )
      strict private
        FCaption: string;
      protected
        constructor Create( const ACaption: string; const AXML: WideString );
      public
        destructor Destroy; override;
        property Caption: string read FCaption;
      end;
    type
      TItemEnumerator = class( TObject )
      strict private
        FOwner: TXmlFileList;
        FIndex: Integer;
      protected
        constructor Create(AOwner: TXmlFileList);
      public
        function GetCurrent: TItem;
        function MoveNext: Boolean;
        property Current: TItem read GetCurrent;
      end;
  strict private
    FXmlFiles: array of TXmlFileList.TItem;
  strict protected
    function GetXmlFile( index: Integer ): TXmlFileList.TItem;
  public
    type TXmlFile = TXmlFileList.TItem;
    constructor Create( );
    destructor Destroy; override;
    function Add( const ACaption: string; const AXML: WideString ): Integer; overload;
    function Add( const AFilename: string ): Integer; overload;
    function Count: Integer;
    function GetEnumerator: TItemEnumerator;
    procedure Clear;
    property XmlFile[ index: Integer ]: TXmlFileList.TItem read GetXmlFile; default;
  end;

And yes, you can start scratching your head when you look at it, but it's a great solution to hide many functions from inexperienced developers! Now they only see what they need to see. (And hopefully they never look at this sourcecode!)
I just expected that I needed to write more code than this...


Why exposing the TItem type with a different name? Actually, these classes are wrapped again in a bigger class TXmlManager that also handles stylesheets, transformations, validations and a bunch of other stuff. TXmlFile is actually exposed at the TXmlManager level, not the TXmlFileList class. TXmlManager contains a few other similar lists where I just re-use the name TItem. There are no conflicts, though, because the parents need to be added to refer to the proper class type.
And while the class header might look complex with nearly 200 lines of code, the rest of the unit is quite slim, with almost 700 lines of code and plenty of comments. The class structure is actually helping me to make it all look simply from the viewpoint of those who use it. Those who use it, don't need to look for types and methods to use. Their choices are just very limited...

0

精彩评论

暂无评论...
验证码 换一张
取 消