Обзор вопросов по языка програмиированию C# и технологии NET
Generics
— это параметризованные типы и методы. Вместо конкретного типа вы пишете параметр типа T
(или несколько), и этот код работает для любого типа, соответствующего ограничениям. Примеры: List<T>
, Dictionary<TKey, TValue>
, void Swap<T>(ref T a, ref T b)
.
Зачем: типобезопасность + отказ от бокcинга для value-types + переиспользуемость кода.
// generic класс
public class Box<T>
{
public T Value { get; set; }
}
// generic метод
public static void Swap<T>(ref T a, ref T b) { var t = a; a = b; b = t; }
// generic интерфейс
public interface IRepository<T> { void Add(T item); T Get(int id); }
При вызове компилятор обычно выводит T
автоматически:
var b = new Box<int> { Value = 5 }; // или просто new Box<int>()
Swap<int>(ref x, ref y); // тип можно явно указать
typeof(List<int>)
и typeof(List<string>)
— разные типы в отражении.Практическое следствие:
typeof(List<>)
→ MakeGenericType(typeof(int))
.default(T)
, typeof(T)
, T[]
и т.д. на уровне IL/runtime.List<int>
и List<double>
— разные скомпилированные версии). Это даёт отсутствие бокcинга и высокую производительность.Следствие:
List<int>
хранит int без boxing).StaticHolder<int>.X
и StaticHolder<string>.X
— разные поля.where T : ...
— позволяет накладывать ограничения на типы-параметры.
Частые варианты:
where T : class
— ссылка (reference type).where T : struct
— non-nullable value type (заметь: int?
не допускается).where T : new()
— должен иметь публичный конструктор без параметров.where T : SomeBaseClass
— наследник указанного класса.where T : ISomeInterface
— реализует интерфейс.where T : unmanaged
— blittable unmanaged type (нет управляемых ссылок).where T : notnull
— запрещает null
(включая nullable reference types).where T : System.Enum
/ where T : System.Delegate
— специальные ограничения для enum/delegate.Пример:
public T CreateInstance<T>() where T : class, new()
{
return new T();
}
Частые вопросы:
new T()
без new()
? — Компилятор не гарантирует, что T
имеет публичный параметр less ctor
.where T : struct
— запрещает Nullabledefault(T)
даёт null
для ссылочных типов, нулевое значение (0
, false
, '\0'
) для value-types.default
сокращённо в коде (контекстно).Объявления out
и in
:
out T
— ковариантность (можно присваивать IEnumerable<string>
в IEnumerable<object>
).in T
— контрвариантность (для делегатов/интерфейсов-«потребителей», например IComparer<in T>
).out/in
к классам.Пример:
IEnumerable<string> ss = new List<string>();
IEnumerable<object> oo = ss; // OK — IEnumerable<out T> ковариантен
Проверки безопасности:
out T
нельзя принимать T
в качестве параметра метода — только возвращать/выдавать.in T
нельзя возвращать T
— только принимать.public void Do<T>(T item) { ... }
Do<int>(5)
.typeof(List<>)
— открытый generic type definition.typeof(List<int>)
— constructed (closed) generic type.Type.IsGenericType
, Type.IsGenericTypeDefinition
, GetGenericArguments()
, GetGenericTypeDefinition()
, MakeGenericType(...)
.MethodInfo.IsGenericMethodDefinition
, MakeGenericMethod(...)
.Пример:
var open = typeof(Dictionary<,>);
var closed = open.MakeGenericType(typeof(string), typeof(int));
constrained.
— помогает вызывать интерфейсные/виртуальные методы на value-types без бокcинга (runtime знает, как это делать).List<T>
vs Array
— array of T удобен и быстрый; generic коллекции лучше для абстракций.new T()
— требует new()
constraint; если нужен non-public ctor — нельзя.List<string>
в List<object>
(это нельзя).string[]
→ object[]
разрешено, но при попытке записать object
в string[]
будет ArrayTypeMismatchException
.where T : struct
≠ where T : new()
— разные семантики.T
— value type и вы приводите List<T>
к IList
/IEnumerable
без generic, могут быть бокcинги/обёртки.Nullable<T>
и where T : struct
— Nullable<T>
НЕ проходит where T : struct
(как правило), потому интервьюер может спросить это как ловушку.(коротко — для использования в интервью)
new T()
? — Только с where T : new()
constraint.out
/in
— позволяет безопасно преобразовать generic интерфейсы/делегаты между совместимыми reference-типами.==
для T
? — Нет гарантии: ==
действует для тех типов, где он определён; лучше использовать EqualityComparer<T>.Default.Equals(a,b)
.Generic repository
public interface IRepository<T> where T : IEntity
{
void Add(T entity);
T? Get(int id);
}
Generic singleton (статик per-T)
public static class SingletonHolder<T> where T : new()
{
public static readonly T Instance = new T();
}
Covariant interface
public interface IReadOnlyList<out T>
{
T Get(int index); // только возвращаем T — разрешено
}
Generic method with interface constraint
public T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) >= 0 ? a : b;
(сформулируй на собесе коротко — и можешь раскрыть, если попросят)
«Generics в .NET — стираются или реальные?» ⇒ Реальные (reified). Метаданные сохраняют параметры типов.
**«Почему List
**«Можно ли присвоить List
**«Сколько статических полей у GenericClass
«Можно ли использовать nullable типы с where T : struct?»
⇒ Нет — where T : struct
запрещает nullable value types (ожидается non-nullable value type).
«Что такое constrained. в IL?» ⇒ Это механизм CLR, позволяющий вызывать виртуальные/интерфейсные методы на value-type без бокcинга.
«Почему иногда JIT шарит код для reference types?» ⇒ Для экономии нативного кода: одна версия кода способна обслуживать разные ссылочные типы.
«Можно ли new T[size] внутри generic метода?»
⇒ Да, new T[n]
разрешён (создаётся массив элементов типа T).
«Equality for T — как правильно?»
⇒ Используйте EqualityComparer<T>.Default
— он корректно обрабатывает value/reference types и пользовательские overrides.
«Почему generic constraints влияют на производительность?» ⇒ Потому что наличие конкретных интерфейсных/методных ограничений позволяет компилятору/CLR генерировать эффективные вызовы (и избежать рефлексии/boxing).
Func<out T>
— ковариантен в возвращаемом типе; Action<in T>
— контрвариантен в параметре.TypeLoadException
.new()
/class
/struct
constraints, почему generics убирают boxing, List<T>
/Dictionary<TKey,TValue>
.constrained.
), performance tradeoffs (code bloat vs speed), advanced constraints (unmanaged
, notnull
), generics + interop/unsafe, memory & JIT internals.✨Dvurechensky✨