We wanted to make a generic TThreadSafeHashSet<T> that would be easy to use with common types (Integer, String, Boolean, Real) while maintaining type safety and proper generic support.
We tried using RTTI (Run Time Type Information) to automatically select the right comparers:
constructor TThreadSafeHashSet.Create;
var
TypeData: PTypeInfo;
begin
TypeData := TypeInfo(T); // Get the type info for T - WORKS
case TypeData^.Kind of // Check the type kind - WORKS
tkInteger: // If it's an integer - WORKS
Create(@IntegerEquals, @IntegerHash); // Use the integer comparer and hash function - DID NOT WORK!
tkString:
Create(@StringEquals, @StringHash);
// ...
end;
end;Problem: Type compatibility issues with generic function types.
We tried using static class functions to create specialized instances:
type
generic TThreadSafeHashSet<T> = class
class static function CreateInteger: specialize TThreadSafeHashSet<Integer>;
class static function CreateString: specialize TThreadSafeHashSet<string>;
// ...
end;Problem: Static methods were a workaround and didn't follow proper OOP patterns.
We tried adding constructors directly to the generic class:
type
generic TThreadSafeHashSet<T> = class
constructor CreateInteger;
constructor CreateString;
// ...
end;Problem: Can't mix generic and specific types in the same class definition.
The working solution uses derived classes for each specific type:
type
// Base generic class
generic TThreadSafeHashSet<T> = class
constructor Create(AEqualityComparer: specialize TEqualityComparer<T>;
AHashFunction: specialize THashFunction<T>);
// ...
end;
// Specialized classes with simple constructors
TThreadSafeHashSetInteger = class(specialize TThreadSafeHashSet<Integer>)
constructor Create; overload;
end;
TThreadSafeHashSetString = class(specialize TThreadSafeHashSet<string>)
constructor Create; overload;
end;- Type Safety: Each specialized class is a concrete type with its own constructor
- Inheritance: Proper use of OOP inheritance patterns
- Encapsulation: Hides the complexity of comparer and hash function setup
- User-Friendly: Simple
Createconstructor for common types - Maintainable: Clear separation between generic and specialized code
// Simple usage with integers
var
Numbers: TThreadSafeHashSetInteger;
begin
Numbers := TThreadSafeHashSetInteger.Create;
try
Numbers.Add(42);
// ...
finally
Numbers.Free;
end;
end;
// Custom type usage still possible with generic version
type
TMyRecord = record
// ...
end;
var
CustomSet: specialize TThreadSafeHashSet<TMyRecord>;
begin
CustomSet := specialize TThreadSafeHashSet<TMyRecord>.Create(@MyRecordEquals, @MyRecordHash);
try
// ...
finally
CustomSet.Free;
end;
end;- Free Pascal's generic system requires careful type matching
- Inheritance can solve problems that generics alone cannot
- Creating specialized classes is better than type aliases for adding functionality
- RTTI-based solutions can be problematic with generics
- Following OOP principles leads to cleaner solutions 👈