Using Generic containers in Delphi XE - always?

后端 未结 7 1211
星月不相逢
星月不相逢 2021-02-03 10:45

Generic containers can be a time saver when having a item, and a strongly typed list of those items. It saves the repetitive coding of creating a new class with perhaps a TList

7条回答
  •  感情败类
    2021-02-03 11:26

    This was prompted by Deltic's answer, I wanted to provide an counter-example proving you can use generics for the animal feeding routine. (ie: Polymorphic Generic List)

    First some background: The reason you can feed generic animals using a generic base list class is because you'll usually have this kind of inheritance:

    TBaseList = class
      // Some code to actually make this a list
    end
    
    TSpecificList = class(TBaseList)
      // Code that reintroduces the Add and GetItem routines to turn TSpecificList
      // into a type-safe list of a different type, compatible with the TBaseList
    end
    

    This doesn't work with generics because you'll normally have this:

    TDogList = TList
    end
    
    TCatList = TList
    end
    

    ... and the only "common ancestor" for both lists is TObject - not at all helpful. But we can define a new generic list type that takes two class arguments: a TAnimal and a TSpecificAnimal, generating a type-safe list of TSpecificAnimal compatible with a generic list of TAnimal. Here's the basic type definition:

    TCompatibleList = class(TObjectList)
    private
      function GetItem(i: Integer): T2;
    public
      procedure Add(A:T2);
      property Item[i:Integer]:T2 read GetItem;default;
    end;
    

    Using this we can do:

    TAnimal = class; 
    TDog = class(TAnimal); 
    TCat = class(TAnimal);
    
    TDogList = TCompatibleList;
    TCatList = TCompatibleList;
    

    This way both TDogList and TCatList actually inherit from TObjectList, so we now have a polymorphic generic list!

    Here's a complete Console application that shows this concept in action. And that class is now going into my ClassLibrary for future reuse!

    program Project23;
    
    {$APPTYPE CONSOLE}
    
    uses
      SysUtils, Generics.Collections;
    
    type
    
      TAnimal = class
      end;
    
      TDog = class(TAnimal)
      end;
    
      TCat = class(TAnimal)
      end;
    
      TCompatibleList = class(TObjectList)
      private
        function GetItem(i: Integer): T2;
      public
        procedure Add(A:T2);
        property Item[i:Integer]:T2 read GetItem;default;
      end;
    
    { TX }
    
    procedure TCompatibleList.Add(A: T2);
    begin
      inherited Add(T1(TObject(A)));
    end;
    
    function TCompatibleList.GetItem(i: Integer): T2;
    begin
      Result := T2(TObject(inherited Items[i]));
    end;
    
    procedure FeedTheAnimals(L: TObjectList);
    var A: TAnimal;
    begin
      for A in L do
        Writeln('Feeding a ' + A.ClassName);
    end;
    
    var Dogs: TCompatibleList;
        Cats: TCompatibleList;
        Mixed: TObjectList;
    
    begin
      try
        // Feed some dogs
        Dogs := TCompatibleList.Create;
        try
          Dogs.Add(TDog.Create);
          FeedTheAnimals(Dogs);
        finally Dogs.Free;
        end;
        // Feed some cats
        Cats := TCompatibleList.Create;
        try
          Cats.Add(TCat.Create);
          FeedTheAnimals(Cats);
        finally Cats.Free;
        end;
        // Feed a mixed lot
        Mixed := TObjectList.Create;
        try
          Mixed.Add(TDog.Create);
          Mixed.Add(TCat.Create);
          FeedTheAnimals(Mixed);
        finally Mixed.Free;
        end;
        Readln;
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
    end.
    

提交回复
热议问题