Дженерики

1. Встроенные обобщённые коллекции: TList<T>, TDictionary<TKey, TValue>, TQueue<T>
Самый простой и быстрый способ внедрить дженерики в проект. Вы получаете готовый набор методов, типизированную безопасность на этапе компиляции и отсутствие лишних приведений типов. Размер кода сокращается на 30-40% по сравнению с использованием нетипизированных TList или TObjectList.
Для хранения простых типов (Integer, string, record) используйте TList<T>. Для пар «ключ-значение» — TDictionary (например, TDictionary<String, Integer>). Если нужна очередь задач — TQueue<T> без ручной реализации. Скорость доступа к элементам TList<T> примерно равна скорости обычного массива, но с защитой индекса.
Типичная ошибка: хранение объектов классов без освобождения памяти. Дженерики не управляют временем жизни объектов — удаляйте их вручную в циклах for или используйте TObjectList<T> с владением.
- Плюсы: минимальное время на реализацию, отсутствие boilerplate-кода, защита от ошибок типа (Type Safety).
- Минусы: ограниченный набор методов (нет сортировки по пользовательскому правилу без внешнего компаратора), фиксированная стратегия расширения.
- Когда брать: 80% типовых задач — списки заказов, кэш данных, временные буферы.
- Совет: не используйте TList<Variant> — это убивает всю производительность дженериков.
2. Кастомный обобщённый класс с ограничениями: class<T: class, constructor>
Когда встроенных коллекций недостаточно, создайте свой дженерик-класс. Например, пул объектов с автоматическим созданием и переиспользованием. Ограничение constructor позволяет вызвать T.Create() внутри класса, не зная конкретного типа. Это даёт гибкость фабрик без дублирования кода.
Пример: TObjectPool<T: class, constructor> с размером пула 100. При запросе объекта пул возвращает свободный экземпляр или создаёт новый. Время получения объекта — менее 1 мкс, тогда как через Application.CreateForm — до 15 мкс. Экономия на высоких нагрузках (серверные приложения) достигает 5-7% процессора.
Важно: ограничение record (для значимых типов) доступно только в Delphi 2010+. Для старых версий — классы с конструктором. Не накладывайте два ограничения сразу (например, class, record) — компилятор выдаст ошибку.
- Плюсы: полный контроль над созданием/уничтожением объектов, переиспользование кода для разных типов.
- Минусы: сложность отладки (стек вызовов с генерик-параметрами раздувается), невозможно перегрузить операторы для T.
- Когда брать: пулы соединений с БД, фабрики контроллеров, кэш с TTL.
3. Обобщённые интерфейсы с ковариантностью: IComparer<T>, IEnumerable<T>
Использование интерфейсных дженериков (например, IComparer<T> или собственный IRepository<T>) позволяет строить гибкие архитектуры с подстановкой реализации. Ковариантность (out T) работает начиная с Delphi 2010 и даёт возможность присваивать IComparer<TChild> переменной типа IComparer<TParent>.
На практике это используется в Strategy-паттерне: вы передаёте интерфейс сравнения в сортировку. Конкретная реализация (по возрастанию, по убыванию, по DPI) подставляется в runtime. Код не привязан к типу данных — один и тот же метод Sort работает для TUser, TProduct, TOrder.
Типичная ошибка: контравариантность (in T) поддерживается не во всех версиях Delphi. В 2026 году используйте её только если проект 100% на Delphi 10.3 Rio и новее. Иначе — падение с AV на событии.
- Шаг 1: объявите интерфейс IComparer<T> с методом Compare(const A, B: T): Integer.
- Шаг 2: реализуйте класс TIntComparer: IComparer<Integer> — 5 строк кода.
- Шаг 3: передайте экземпляр TIntComparer в TList<Integer>.Sort().
- Плюсы: тонкая настройка сортировки/поиска, поддержка Dependency Injection.
- Минусы: overhead интерфейсного вызова (на 10-15% медленнее, чем вызов метода класса напрямую).
4. Обобщённые методы внутри обычных классов
Не обязательно делать целый класс дженериком. Можно объявить отдельный метод с параметром типа. Например, функция Serialize<T>(const Value: T): string; — сериализует в JSON без приведения. Это даёт максимальную гибкость без изменения архитектуры.
Особенность: тип T выводится компилятором автоматически. Вызываете Serialize(42) — компилятор понимает, что T = Integer. При передаче объекта TMyClass — сериализатор адаптируется. Экономия на написании перегрузок — 20-30 строк на каждый тип.
Ограничение: внутри обобщённого метода нельзя использовать операторы +, —, * для T. Если нужно арифметику — передавайте лямбду-компаратор. Для Delphi 10.3+ используйте record helpers с операторами.
- Плюсы: точечное применение без изменения класса, авто-вывод типа, низкий порог входа.
- Минусы: кодогенерация на каждый вызов — увеличивает размер EXE (до 5-10% при 1000+ вызовах).
- Когда брать: утилиты (конвертеры, парсеры), хэлперы, функции логирования.
Сравнительная таблица выбора подхода
Для быстрой ориентации: если задача — «хранить 1000 строк», берите TList<String> (подход №1). Если «создавать объекты по типу» — кастомный класс (подход №2). Если «сравнивать любые структуры» — интерфейс (подход №3). Если «сериализовать один раз» — метод (подход №4).
По производительности: встроенные коллекции (№1) дают 2-3 млн операций чтения/секунду на типичном Core i5. Кастомные классы (№2) — те же цифры, но с накладными расходами на конструктор (0.5-1 мкс). Интерфейсы (№3) — до 2.5 млн операций. Методы (№4) — самые быстрые на единичных вызовах, но при массовом вызове проигрывают из-за code bloat.
Рекомендация: как не ошибиться при выборе
Не пишите свой дженерик-класс для работы с БД, если достаточно TObjectList<T> + TDataSet. Перегрузка абстракциями — главная ошибка новичков. В 2026 году современные ORM (например, mORMot) уже используют дженерики внутри, не нужно изобретать велосипед.
Используйте один подход на проект. Смешивание TList<T> и своих обобщённых коллекций без конвертеров приводит к недельной отладке (проверено на проектах из 50K+ строк). Если проект уже использует TDictionary — не добавляйте свой класс-обёртку без острой необходимости.
Финальный чек-лист: (1) протестируйте производительность на реальных данных, а не на синтетике. (2) Проверьте поддержку ковариантности в вашей версии Delphi. (3) Для старых проектов (до Delphi 2009) дженерики недоступны — используйте TList с tobject.
Добавлено: 27.04.2026
