As a first example of a generic class, I've implemented a key-value pair data structure. The first code snippet below shows the data structure written in a traditional fashion, with an object used to hold the value: type
TKeyValue = class private
FKey: string; FValue: TObject;
procedure SetKey(const Value: string); procedure SetVa1ue(const Value: TObject); publ ic property Key: string read FKey write SetKey; property Value: TObject read FValue write SetValue; end;
To use this class you can create an object, set its key and value, and use it, as in the following snippets of various methods of the main form of the Key-ValueClassic example:
ShowMessage( '[' + kv.Key ++ kv.Value.ClassName + ']');
Generics make it possible to use a much broader definition for the value, but that's not the key point. What's totally different, as we'll see, is that once you've instantiated the key-value generic class, it becomes a specific class, tied to a given data type. This makes your code type safer, but I'm getting ahead of myself. Let's start with the syntax used to define the generic class: type
TKeyVa1ue<T> = class private
FKey: string; FValue: T;
procedure SetKey(const Value: string); procedure SetVa1ue(const Value: T); publ ic property Key: string read FKey write SetKey; property Value: T read FValue write SetValue; end;
In this class definition, there is one unspecified type, indicated by the placeholder T71. The symbol T is frequently used by convention, but as far as the compiler is concerned you can use just any symbol you like. Using T generally makes the code more readable when the generic class uses only one parametric type; in case the class needs multiple parametric type it is common to name them according to their actual role, rather than using a sequence of letters (T, U, V) as it happened in C++ during the early days.
The generic TKeyVa1ue<T> class uses the unspecified type as the type of one of its two fields, the property value, and the setter method parameter. The methods are defined as usual, but notice that regardless of the fact they have to do with the generic type, their definition contains the complete name of the class, including the generic type:
procedure TKeyVa1ue<T>.SetKey(const Value: string); begi n
procedure TKeyVa1ue<T>.SetVa1ue(const Value: T); begi n
71 "T" has been the standard name, or placeholder, for a generic type since the days the C++ language introduced templates in the early 1990s. Depending on the authors, the "T" stands for either "Type" or "Template type".
To use the class, instead, you have to fully qualify it, providing the actual value of the generic type. For example, you can now declare a key-value object hosting buttons as value by writing: kv: TKeyValue<TButton>;
The full name is required also when creating an instance, because this is the actual type name (while the generic, uninstantiated type name is like a type construction mechanism).
Using a specific type of the value of the key-value pair makes the code much more robust, as you can now only add TButton (or derived) objects to the key-value pair and can use the various methods of the extracted object. These are some snippets from the main form of the KeyValueGeneric example: // FormCreate kv := TKeyValue<TButton>.Create;
kv.Value := Sender as TButton;
// Button2Click kv.Value := Sender as TButton; // was "self" // Button3Click
ShowMessage ( '[' + kv.Key + + kv.Value.Name + ']');
When assigning a generic object in the previous version of the code we could add either a button or a form, now we can use only button, a rule enforced by the compiler. Likewise, rather than a generic kv.Value.ClassName in the output we can use the component Name, or any other property of the TButton class.
Of course, we can also mimic the original program by declaring the key-value pair as: kvo: TKeyValue<TObject>;
In this version of the generic key-value pair class, we can add any object as value. However, we won't be able to do much on the extracted objects, unless we cast them to a more specific type. To find a good balance, you might want to go for something in between specific buttons and any object, requesting the value to be a component: kvc: TKeyValue<TComponent>;
You can see corresponding code snippets in the same KeyValueGeneric demo program. Finally, we can also create an instance of the generic key-value pair class that doesn't store object values, but rather plain integers: var kvi: TKeyVa1ue<Integer>; begi n kvi := TKeyVa1ue<Integer>.Create; try kvi.Key := 'object'; kvi.Value := 100; kvi.Value := Left; ShowMessage ( '[' + kvi.Key + + IntToStr (kvi.Value) + ']'); fi nal ly kvi.Free;
Was this article helpful?
What you need to know about… Project Management Made Easy! Project management consists of more than just a large building project and can encompass small projects as well. No matter what the size of your project, you need to have some sort of project management. How you manage your project has everything to do with its outcome.