Обобщения (Generics)
Обобщения параметризуют тип или метод аргументом типа T, чтобы одно определение вроде List<T> работало с любым типом, оставаясь строго типизированным. Это убирает главную беду контейнеров на object — приведения при чтении и упаковку значимых типов — и переносит проверку типов на этап компиляции.
Ключевая идея, которая отличает C# от Java: обобщения C# овеществлены (reified). CLR хранит настоящий аргумент типа в рантайме, а JIT специализирует код под каждый значимый тип, поэтому List<int> держит настоящие int без упаковки, а T можно восстановить через рефлексию. Вокруг этого выстраивается вся тема: обобщённые методы с выводом типа, ограничения where, обобщённые коллекции, контракт IEnumerable<T> как основа ленивых последовательностей и оператор default(T) для «нулевого» значения открытого типа. Полная карта — в слоях ниже.
Карта темы
- Обобщения — параметризация типом
T, типобезопасность без упаковки, овеществление в рантайме против стирания Java. - Обобщённые методы — собственные параметры типа метода, вывод
Tиз аргументов, обобщённый метод в необобщённом классе. - Ограничения —
where T:сужает допустимые аргументы и разблокирует вызовы членов;class,struct,new(), базовый класс, интерфейс,notnull. - Обобщённые коллекции —
List<T>,Dictionary<TKey,TValue>,HashSet<T>: один известный тип, без упаковки, в отличие отArrayList. - IEnumerable — контракт
GetEnumerator, однонаправленный перебор для чтения, основа отложенного выполнения LINQ иyield return. - Оператор default —
default(T)даёт нулевое значение:null,0/falseили обнулённая структура; зачем это открытомуT.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Считать обобщения контейнерами на object с сахаром | Упускается проверка типов на компиляции; List<int> НЕ упаковывает элементы |
Думать, что C# стирает T, как Java | В C# T овеществлён и восстановим через рефлексию; JIT специализирует под значимые типы |
Всегда прописывать <T> в вызове | Компилятор обычно выводит T из аргументов; явный <T> нужен, лишь когда вывод невозможен |
| Считать, что обобщённый метод — только у обобщённого класса | Его может объявить и обычный класс |
Считать, что where проверяется в рантайме | Ограничения проверяет компилятор заранее; без них тело трактует T лишь как object |
Ждать, что default(T) всегда null | Для значимых типов это 0/false/обнулённая структура, а не null |
Думать, что default(T) вызывает конструктор | Оно лишь обнуляет память, не запуская конструктор и инициализаторы полей |
Значение для собеседований
Обобщения — обязательная тема для middle и выше. Кандидат, который объясняет, почему List<int> не упаковывает элементы и чем овеществление C# отличается от стирания Java, показывает понимание рантайма, а не только синтаксиса.
Что обычно проверяют:
- Зачем обобщения вместо
object: типобезопасность на компиляции, переиспользование, отсутствие упаковки. - Овеществление в C# против стирания в Java и роль JIT-специализации.
- Обобщённый метод и вывод типа из аргументов.
- Что делают ограничения
whereи какие бывают. - Контракт
IEnumerable<T>и отложенное (ленивое) выполнение. - Что даёт
default(T)и почему это не всегдаnull.
Типичный неверный ответ: «Обобщения — это просто object с красивым синтаксисом». Это открывает разговор о проверке типов на компиляции, отсутствии упаковки значимых типов и о том, что T в C# доживает до рантайма и восстановим через рефлексию.