Delphi Generics: When Type Safety Meets Binary Size
Delphi’s generic types give compile‑time safety and clean code, but they can inflate your binary. Learn how to use generics, constrain types, and measure the impact in a real example.
09 May 2026, 04:37 UTC

Problem: Cast‑heavy code and hidden runtime errors
Legacy Delphi projects often store objects in TList or TDictionary and then cast them back to the concrete type. A wrong cast can crash the application at runtime and is hard to spot in static analysis. The question is: can we keep the same collection semantics while eliminating casts and catching errors at compile time?
Thesis: Use Delphi generics for type‑safe collections and algorithms, but be mindful of binary size and compiler constraints.
1. Syntax and Basic Usage
Generics in Delphi follow a C#‑style syntax. The compiler generates a concrete implementation for each type parameter you use. Here’s a minimal example that creates a list of integers, adds 1,000 elements, and prints the sum.
program GenericSample;
uses
System.SysUtils, System.Generics.Collections;
var
Numbers: TList<Integer>;
I, Sum: Integer;
begin
Numbers := TList<Integer>.Create;
try
for I := 1 to 1000 do
Numbers.Add(I);
Sum := 0;
for I in Numbers do
Sum := Sum + I;
Writeln('Sum = ', Sum);
finally
Numbers.Free;
end;
end.
Compile and run. The output should be Sum = 500500. No casting is required, and the compiler guarantees that only integers can be added to the list.
2. Constrained Type Parameters
Delphi lets you restrict the type parameter to a class, interface, or base type. This allows you to call methods that are guaranteed to exist.
type
TMyInterface = interface
['{12345678-1234-1234-1234-123456789ABC}']
procedure DoWork;
end;
TWorkerList<T: TMyInterface> = class
private
FList: TList<T>;
public
constructor Create;
procedure AddWorker(const AWorker: T);
procedure ExecuteAll;
end;
constructor TWorkerList<T>.Create;
begin
inherited Create;
FList := TList<T>.Create;
end;
procedure TWorkerList<T>.AddWorker(const AWorker: T);
begin
FList.Add(AWorker);
end;
procedure TWorkerList<T>.ExecuteAll;
var
Worker: T;
begin
for Worker in FList do
Worker.DoWork;
end;
Because T is constrained to TMyInterface, the compiler will reject any type that does not implement DoWork. This eliminates a whole class of runtime errors.
3. Trade‑off: Binary Size Growth
Generics are implemented via compile‑time templates. For each distinct type parameter the compiler emits a new version of the class or method. In a small utility that uses only TList<Integer>, the size increase is negligible. However, if you instantiate dozens of generic types—say TList<Integer>, TList<String>, TDictionary<String, TObject>, etc.—the executable can grow by several megabytes.
To gauge the impact:
- Compile the application without generics (use plain
TListand cast). Note the.exesize. - Replace the collections with generics. Recompile and compare sizes.
- Use the IDE’s Project > Compile output to see the size difference.
For a medium‑sized project, the increase is usually 1–3 %, but for tiny utilities it can be noticeable.
4. Trade‑off: Compatibility and RTTI Overhead
Delphi generics were introduced in Delphi 2009. If you must maintain compatibility with older compilers, guard your generic code with compiler directives:
{$IF CompilerVersion >= 23.0}
// Delphi 2009 and newer
uses System.Generics.Collections;
{$ENDIF}
When you use generics together with RTTI (e.g., TRttiContext to inspect generic types), the compiler emits extra metadata. In large projects this can increase startup time by a few hundred milliseconds. Measure startup time with the Run > Show Runtime Statistics option to see if RTTI is a bottleneck.
Actionable Closing
- Start by refactoring one collection: replace
TListwithTList<T>and verify no casts are needed. - Measure the binary size before and after. If the increase is acceptable, apply generics consistently.
- For legacy code that cannot be changed, keep generics in new modules and isolate them behind interfaces.
- Document constrained type parameters so future developers know the required interface or base class.
- Use compiler directives to maintain backward compatibility if needed.
By balancing type safety against binary size and compiler support, you can modernize Delphi codebases without unexpected surprises.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.