Generic Types and Classes in Delphi

Generic Classes in Delphi
Generics are powerful language features that allows to write type-safe classes, methods that act upon a type to be provided later. Generic classes makes it possible to bind a specific type with a class that means we can instantiate a class tied to a given data type. This makes code type safer. We can declare  generic type to accept any type, or we can use constraints, which will limit the types that can be passed to ones with parameterless constructors, or types of a specific class or those that implement specific interfaces. Once we define a specific type on generic declaration, this is enforced by the compiler that we cannot assign a different type value to the class member. Generic classes are as efficient as normal classes but in some case it is more efficient than other classes as the need for runtime type cast is reduced. Generally we use Generics  on cases when we need lots of type casting with a specific type or we are writing a lots of sub classes to handle specific case of specific type.  

For easy understanding lets start with an example....
A simple Delphi class to store key and values..... 
type
TKeyValue = class
private
FKey: integer;
FValue: TObject;
public
property Key: integer read FKey write FKey;
property Value: TObject read FValue write FValue;
end;

To use above class we can create an object, set its key and value, and use it, shown in the following code snippets:
// Object declare //
var
Obj1: TKeyValue;
// FormCreate
Obj1 := TKeyValue.Create;
// Button1Click
Obj1.Key := 1;
Obj1.Value := TObject(1);
// Button2Click
Obj1.Key := 2;
Obj1.Value := TObject('Delphi');

Now creating a Generic class that can be instantiated with given data type
type
TKeyValue<T> = class
private
FKey: integer;
FValue: T;
public
property Key: integer read FKey write FKey;
property Value: T read FValue write FValue;
end;

In Delphi, Generic classes are declared using <T> keyword. <T> defines a specific type that is given during class instantiate later. And the Generic TKeyValue<T> class uses that type as type of class member declared as type T. The class member are defined as usual; however, even though they have to do with the generic type, their definition contains the complete name of the class, including the generic type.

For example if we declare a setter method for a property then...
procedure TKeyValue<T>.SetKey(const Value: integer);
begin
FKey := Value;
end;

To use above generic class we can declare an object with a given data type to host that type as value. Using a specific type of the value for above class makes the code much more robust, as now we can only add integer objects to the value.
var
Obj1: TKeyValue<Integer>;
// FormCreate
Obj1 := TKeyValue<Integer>.Create;
// Button1Click
Obj1.Key := 1;
Obj1.Value := 1;
// Button2Click
Obj1.Key := 2;
Obj1.Value := 'Delphi';  // Note* - will get Compile error for type cast //
Generic Class Constraints
The main problem with generic type definition is that we can do very little with the objects of the generic types as we just declare a Generic class with <T> only. So to overcome this issue we can define a Generic class with constraints of the types that we want to use. By specifying a class constraint, we indicate that we can use only object types as generic types.

So we can declare a Generic class with class constraints like...
type
TSampleClass <T: class> = class
type
TSampleClass <T: integer> = class
type
TSampleClass <T: string> = class
for example if we declare a Generic class like follow...
type
        TCompClass <T: TComponent> = class

The instances of this generic class can be applied only to component classes that is any TComponent descendant class. This lets the compiler to let us use all of the methods of the TComponent class while working on the generic type.

Using Predefined Generic Containers in Delphi
In early Delphi versions we can use TObjectList container to store list of different kind of objects. If we want to define a specific type of container then we can do it by inheritance but this is a teddy job. In Delphi 2009 a small set of Generic Container Classes are defined in Generics.Collections unit which we can use to store list of specific type of objects.

Following are 4 main Generic Container Classes
type
TList<T> = class
TQueue<T> = class
TStack<T> = class
TDictionary<TKey,TValue> = class
USING TLIST<T>
The sample program has a unit that defines a TDate class and the main form is used to refer to a TList of dates. As a starting point, I added a uses clause referring to Generics.Collections, and then I changed the declaration of the main form field to:
private
ListDate: TList <TDate>;
Of course, the main form OnCreate event handler that creates the list needs to be updated as well, becoming:
procedure TForm1.FormCreate(Sender: TObject);
begin
ListDate := TList<TDate>.Create;
end;
Now we can try to compile the rest of the code as it is. The program has a “wanted” bug, trying to add a TButton object to the list. The corresponding code used to compile, now fails:
procedure TForm1.Button1Click(Sender: TObject);
var
Date1, Date2: Tdate;
begin
// add a button to the list
ListDate.Add (Sender); // Error:
// E2010 Incompatible types: 'TDate' and 'TObject'
Date1 := Tdate.Create;
ListDate.Add (Date1);
Date2 := Tdate.Create;
ListDate.Add (Date2);
end;

How to free a generic TList<T>?
MyList.Free is sufficient if we use TObjectList else we have to free list items also.

Interestingly, TList<T> does not provide the same set of methods as TList does. We have more "high-level" methods at our disposal, but less "low-level" methods (as Last, which is missing). The new methods are the following:
• 3 overloaded versions of AddRange, InsertRange and DeleteRange: they are equivalent to Add/Insert/
Delete respectively, but for a list of elements;
• A Contains method;
• A LastIndexOf method;
• A Reverse method;
• 2 overloaded versions of Sort (whose name is not new, but whose usage is quite different);
• 2 overloaded versions of BinarySearch.

How to set a forward declaration with generic types under Delphi 
Suppose we have an item and a collection class, both referencing each other, that require a forward declaration then we can do it by following way...
type
  TBaseElement = class // base class
  end;
type
  TMyCollection<T: TBaseElement> = class // base generic class
  end;
type
  TMyElement = class(TBaseElement) // actually class having genric type object
  private
    FParent: TMyCollection<TBaseElement>;
  end;

Example
type
  { Declare a new object type. }
  TNewObject = class
  private
    FName: String;
   public
    constructor Create(const AName: String);
    destructor Destroy(); override;
  end;
{ TNewObject }
constructor TNewObject.Create(const AName: String);
begin
  FName := AName;
end;
destructor TNewObject.Destroy;
begin
  { Show a message whenever an object is destroyed. }
  writeln('Object "' + FName + '" was destroyed!');
  inherited;
end;
var
  List: TObjectList<TNewObject>;
  Obj: TNewObject;
begin
  { Create a new List. }
  { The OwnsObjects property is set by default to true -- the list will free the owned objects automatically. }
  List := TObjectList<TNewObject>.Create();
 { Add some items to the List. }
  List.Add(TNewObject.Create('One'));
  List.Add(TNewObject.Create('Two'));
{ Add a new item, but keep the reference. }
  Obj := TNewObject.Create('Three');
  List.Add(Obj);
{
    Remove an instance of the TNewObject class. The destructor
    is called for the owned objects, because you have set the OwnsObjects
    to true.
  }
  List.Delete(0);
  List.Extract(Obj);
{ Destroy the List completely -- more message boxes will be shown. }
  List.Free;
  readln;
end.

Comments

Post a Comment

Popular posts from this blog

ShellExecute in Delphi

How to send Email in Delphi?

Variants in Delphi. Use of Variant Array. How to check a Variant is unassigned or empty or clear ?