Stop Casting Everything: Using Delphi Generics to Eliminate Type-Safety Risks
Stop relying on risky TObject casts. Learn how to use Delphi Generics to move type-checking from runtime to compile-time, reducing crashes and boilerplate code.
24 Aug 2025, 22:33 UTC

The Cost of the 'TObject' Pattern
For years, Delphi developers handled collections of different object types by using TList or TObjectList. Because these collections store everything as a TObject, every single item retrieved from the list requires a type cast to be useful. This pattern introduces two primary risks: runtime EInvalidCast exceptions if the wrong object type is inserted, and a significant amount of repetitive boilerplate code.
The solution is Generics. By defining a type parameter, you shift the responsibility of type checking from the runtime (where it crashes your app) to the compiler (where it prevents the build from succeeding until the code is correct).
How Generics Change the Implementation
Generics allow you to write a class or method where the specific data type is decided at the moment of instantiation rather than during the initial coding phase. In Delphi, this is denoted by the <T> syntax, where T is a placeholder for the type.
Type Safety vs. Manual Casting
In a non-generic TList, the compiler cannot stop you from adding a TButton to a list intended for TCustomer objects. You only discover the error when you attempt to cast that button back to a customer during a loop. A TList<TCustomer> makes this impossible; the compiler will flag any attempt to add a non-customer object as a syntax error.
Using Constraints with 'specializes'
Sometimes a generic type is too broad. If your generic class needs to call a specific method—for example, a SaveToDB method—you cannot use a raw <T> because the compiler doesn't know if T possesses that method. You can solve this using constraints. By specifying that T must inherit from a specific base class or implement a specific interface, you regain access to those members within the generic class.
Worked Example: A Generic Repository
Consider a scenario where you need to manage different data entities (Users, Products, Orders) using the same basic CRUD (Create, Read, Update, Delete) logic. Instead of writing three separate manager classes, you can create a generic repository.
type
// A base class to ensure all entities have an ID
TEntity = class
ID: Integer;
end;
// T is constrained to TEntity or its descendants
TRepository<T: TEntity> = class
private
FData: TList<T>;
public
constructor Create;
destructor Destroy; override;
procedure AddEntity(AEntity: T);
function FindByID(AID: Integer): T;
end;
implementation
constructor TRepository<T>.Create;
begin
FData := TList<T>.Create;
end;
destructor TRepository<T>.Destroy;
begin
FData.Free;
inherited;
end;
procedure TRepository<T>.AddEntity(AEntity: T);
begin
FData.Add(AEntity);
end;
function TRepository<T>.FindByID(AID: Integer): T;
var
Item: T;
I: Integer;
begin
Result := nil;
for I := 0 to FData.Count - 1 do
begin
Item := FData[I];
if Item.ID = AID then
Break;
end;
Result := Item;
end;
Implementation Details
- Where to run: This code is implemented within the application source logic.
- Permissions: Standard developer access to the Delphi IDE.
- Placeholders:
Trepresents any class descending fromTEntity. - Expected Check: Attempting to instantiate
TRepository<TStringList>should trigger a compiler error becauseTStringListdoes not inherit fromTEntity.
Trade-offs and Limitations
While generics reduce code duplication, they are not a silver bullet. One primary trade-off is compilation time. Because the Delphi compiler generates a specialized version of the class for every unique type used (a process similar to templates in C++), projects with hundreds of unique generic instantiations may see slower build times.
Additionally, generics are not supported in versions of Delphi prior to 2009. If you are maintaining a legacy codebase from that era, you will need to upgrade the environment before implementing these patterns.
Verifying the Result
To verify that your generic implementation is working as intended, perform a "Negative Test": try to pass an incompatible type into your generic collection. If the IDE highlights the line in red and prevents compilation, the type safety is active. If the code compiles but crashes at runtime, you are likely still using a non-generic TList or an unsafe cast internally.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.