- Expert Delphi
- Pawe? G?owacki
- 803字
- 2021-07-02 20:44:23
Generics
One of the most powerful concepts in the Object Pascal language is a generic type. This way, we can write our code in a more generic way, so the same algorithm can operate not on just one data type, but many. Generics are things that can be parameterized by type. The code is not fully specified, providing the implementation details to the code that uses generics. There could be generic types where the whole type definition is parameterized by an unknown type, or we can just define generic methods that operate on a type that is not fully specified.
As an example, let's consider a fillable class. Many languages have the concept of nullable values, but we will be different and use "fillable". It seems more natural. One useful example of using nullables or fillables is mapping between the data stored in a relational database and the entities in our code. A field in a database record may contain a value, such as a string or an integer, or it might be null. In order to properly represent the data coming from a database in our code, we need an extra logical flag that will say whether a value of the given type contains a valid value or null. If we represented this data using an object, we would have the possibility to have a nil reference, but it is more efficient to work with plain, simple built-in types the lifetime of which does not need to be directly managed. Without generics, we would need to implement a fillable class for every field type duplicating the same logic for clearing the flag, returning information if the value is filled or adding two fillables together:
type TFillable<T> = record Value: T; IsFilled: boolean; end; TFillableString = TFillable<string>; TFillableInteger = TFillable<integer>; // ...
If our implementation cannot deal with arbitrary types, we can specify a constraint on a type parameter using a colon. We can, for example, say that a generic type TFmxProcessor can be parameterized by any type, but it needs to be a class derived from TFMXObject, and it needs to have a public constructor:
type TFmxProcessor<T: TFMXObject, constructor> = class // ... end; TRecordReporter = class procedure DoReport<T: record>(x: T); end;
Another example where generics are useful is a custom sorting algorithm. There are many possible implementations, such as bubble sort or quick sort. It does not matter if we are sorting characters, integers, or real numbers. The algorithm's logic is the same. With generics, you do not need to implement the sorting algorithm for all the possible types it can operate on. You just need to have a way to compare two values, so they can be ordered properly.
Delphi comes with the System.Generics.Collections unit, which defines many useful generic collection types, such as enumerations, lists, and dictionaries that we can use in our code.
Consider the following TPerson class:
unit uPerson; interface type TPerson = class FirstName, LastName: string; constructor Create(AFirstName, ALastName: string); function Fullname: string; end; implementation { TPerson } constructor TPerson.Create(AFirstName, ALastName: string); begin FirstName := AFirstName; LastName := ALastName; end; function TPerson.Fullname: string; begin Result := FirstName + ' ' + LastName; end; end.
Before the introduction of generics, you could manage a list of objects with the TList class. Let's check out the differences between managing lists with and without generics.
Here is some code that iterates through an object list of the TPerson instance and logs their full names using TList, as shown in the following code snippet:
procedure DoPersonsTList; var persons: TList; p: TPerson; i: integer; begin persons := TList.Create; try // not safe, can add any pointer persons.Add(TPerson.Create('Kirk', 'Hammett')); persons.Add(TPerson.Create('James', 'Hetfield')); persons.Add(TPerson.Create('Lars', 'Ulrich')); persons.Add(TPerson.Create('Robert', 'Trujillo')); for i := 0 to persons.Count-1 do begin p := persons.Items[i]; Log(p.Fullname); end; finally persons.Free; end; end;
TList defined in the System.Classes unit can be used to manage a list of pointers of an arbitrary type. In order to access the Fullname method of the TPerson class, we need to perform a typecast by assigning a pointer reference to a variable of a proper type. If the object is not TPerson or its descendant, we will get an error at runtime, as shown in the following code snippet:
procedure DoPersonsGenerics; var persons: TObjectList<TPerson>; p: TPerson; begin persons := TObjectList<TPerson>.Create; try // safe, can only add TPerson or descendant persons.Add(TPerson.Create('Kirk', 'Hammett')); persons.Add(TPerson.Create('James', 'Hetfield')); persons.Add(TPerson.Create('Lars', 'Ulrich')); persons.Add(TPerson.Create('Robert', 'Trujillo')); for p in persons do Log(p.Fullname); // no typecast needed finally persons.Free; end; end;
Using a generic list is much cleaner. The compiler, at compile time, knows that it deals with a list of TPerson objects and will not compile code that tries to add incompatible references. We can use the more readable for..in..do loop, and there is no need for a typecast. Using generics, in general, improves the quality of your code.
- Node.js Design Patterns
- C# 2012程序設計實踐教程 (清華電腦學堂)
- 編寫高質量代碼:改善Python程序的91個建議
- Learning Neo4j 3.x(Second Edition)
- Windows Server 2016 Automation with PowerShell Cookbook(Second Edition)
- C語言程序設計同步訓練與上機指導(第三版)
- Access 2010數據庫應用技術(第2版)
- Raspberry Pi Blueprints
- Getting Started with the Lazarus IDE
- Visual FoxPro程序設計實驗教程
- Learning Yeoman
- MATLAB語言及編程實踐:生物數學模型應用
- Python語言及其應用(第2版)
- Abaqus GUI程序開發指南(Python語言)
- 零基礎學C++