Q1什么是泛型为什么要用泛型出题意图考察对泛型基本概念的理解以及是否能说清楚它解决了什么问题。答泛型是一种延迟声明类型的机制。声明时用占位符T代替具体类型调用时再指定。引入泛型主要解决两个问题性能问题用object作为通用参数时值类型会发生装箱/拆箱有额外的堆分配和 CPU 开销。泛型在 CLR 层面对值类型生成专用代码完全避免装箱性能等同于专用方法。类型安全问题object参数在编译期无法检查类型强制转换错误只能在运行时暴露。泛型在编译期就能检查类型错误提前发现。// 泛型方法一个方法任意类型无装箱编译期安全 public static void ShowT(T value) { Console.WriteLine(${typeof(T).Name}: {value}); } Showint(123); // 显式指定 Show(hello); // 类型推导省略尖括号Q2泛型是语法糖吗底层原理是什么出题意图区分候选人是否真正理解泛型的实现机制还是只会用。答泛型不是语法糖。语法糖是编译器提供的便捷写法编译后转换成等价的普通代码。泛型是由CLR 在底层真正支持的从 .NET Framework 2.0 开始引入编译器和运行时都必须支持。底层机制编译器将泛型类/方法编译成带占位符的 IL 代码例如List\1[T]、Dictionary2[TKey,TValue]JIT 编译时值类型(int、struct等)CLR 为每种类型生成一份独立的本地代码不存在装箱引用类型CLR 共享同一份本地代码(引用类型内存布局相同都是指针大小)这就是为什么泛型方法性能等同于专用方法而远优于object方法。Q3泛型方法的性能为什么优于 object 参数方法出题意图考察对装箱拆箱机制的理解以及泛型性能优势的具体原因。答核心原因是装箱拆箱。值类型(如int)传入object参数时需要在堆上分配新对象(装箱)取出时再拆箱有额外的内存分配和 GC 压力。泛型方法对值类型生成专用代码直接操作无需装箱。int value 123; // Object方式每次调用都装箱 ShowObject(value); // int → object(堆分配)→ int // 泛型方式无装箱直接操作 int Showint(value);性能差异在大量循环 值类型场景下最明显例如 1 亿次调用object方法耗时可能是泛型方法的数倍。Q4泛型约束有哪些分别有什么作用出题意图考察泛型约束的掌握程度这是实际开发中经常用到的。答约束语法作用基类约束where T : PeopleT 必须是 People 或其子类方法内可访问 People 的成员接口约束where T : ISportsT 必须实现该接口方法内可调用接口方法引用类型约束where T : classT 只能是引用类型值类型约束where T : structT 只能是值类型且可以直接new T()无参构造约束where T : new()T 必须有无参构造函数方法内可new T()枚举约束where T : EnumT 必须是枚举类型父子关系约束where T : ST 必须是 S 或 S 的子类多个约束可以叠加// T 必须是引用类型、实现 IEntity 接口、有无参构造函数 public class RepositoryT where T : class, IEntity, new() { public T Create() new T(); }解答思路回答时结合实际场景比如基类约束让我在泛型方法里能访问特定属性避免了强制转换new()约束让我可以在方法内部创建实例。Q5以下代码有什么问题如何用泛型约束解决public class RepositoryT { public T Create() { return new T(); // 编译错误 } public void Save(T entity) { Console.WriteLine(entity.Id); // 编译错误 } }出题意图考察泛型约束的实际应用以及如何通过约束解决编译时类型安全问题。答两个错误原因new T()编译器不知道 T 是否有无参构造函数entity.Id编译器不知道 T 是否有Id属性解决方案public interface IEntity { int Id { get; set; } } // 加上约束T 必须是引用类型、实现 IEntity、有无参构造函数 public class RepositoryT where T : class, IEntity, new() { public T Create() { return new T(); // ✓ } public void Save(T entity) { Console.WriteLine(entity.Id); // ✓ 可以访问 Id } }解答思路识别编译错误的根本原因 → 选择对应约束 → 理解约束组合使用。Q6ListAnimal和ListCat有继承关系吗为什么出题意图考察对泛型类型系统的理解引出协变逆变话题。答没有继承关系。即使Cat继承自AnimalListCat和ListAnimal也是完全独立的两个类型ListAnimal list new ListCat(); // 编译报错原因泛型类用不同类型参数实例化后得到的是不同的类型彼此没有任何关系。这是 C# 类型系统的设计目的是保证类型安全——如果允许上面的赋值就可以往list里Add(new Dog())但实际底层是ListCat运行时会崩溃。如果需要这种灵活性应该使用协变(IEnumerableout T)。Q7什么是协变和逆变请解释以下代码的编译结果。// 片段1 ListAnimal list1 new ListCat(); // ? // 片段2 IEnumerableAnimal list2 new ListCat(); // ? // 片段3 public interface IReaderout T { T Read(); } IReaderAnimal reader new ReaderCat(); // ? // 片段4 public interface IWriterin T { void Write(T item); } IWriterCat writer new WriterAnimal(); // ?出题意图这是中高级 .NET 面试的高频考点考察对协变逆变的真实理解。答片段1编译错误❌ —ListT不支持协变片段2编译成功✓ —IEnumerableout T支持协变片段3编译成功✓ —out T协变右边子类左边父类片段4编译成功✓ —in T逆变右边父类左边子类协变(out)T 只能作为返回值允许右边用子类左边用父类。public interface ICustomerListOutout T { T Get(); // ✓ 可以作为返回值 // void Show(T t); // ✗ 不能作为参数 } ICustomerListOutAnimal list new CustomerListOutCat(); // ✓逆变(in)T 只能作为参数允许右边用父类左边用子类。public interface ICustomerListInin T { void Show(T t); // ✓ 可以作为参数 // T Get(); // ✗ 不能作为返回值 } ICustomerListInCat list new CustomerListInAnimal(); // ✓记忆口诀out协变出去的(返回值)子类 → 父类出口放宽in逆变进来的(参数)父类 → 子类入口放宽注意协变逆变只适用于泛型接口和泛型委托不适用于泛型类。Q8协变逆变为什么只适用于接口和委托不适用于泛型类出题意图考察对协变逆变设计原理的深层理解区分中高级候选人。答因为泛型类的类型参数既可以出现在参数位置也可以出现在返回值位置无法同时满足协变和逆变的安全约束。以ListT为例它既有Add(T item)(T 做参数)又有T this[int index](T 做返回值)。如果允许协变ListAnimal list new ListCat(); // 假设合法 list.Add(new Dog()); // 往 ListCat 里加了一只 Dog类型系统崩溃而接口可以通过in/out约束明确规定 T 只出现在参数或返回值的某一侧编译器在定义接口时就能验证安全性。IEnumerableout T只有GetEnumerator()(T 只做返回值)所以可以协变。IListT因为有Add(T)和T this[int]所以不支持协变。Q9泛型缓存和字典缓存有什么区别出题意图考察对泛型静态成员特性的理解以及实际应用能力(ORM、框架开发场景)。答字典缓存用一个静态DictionaryType, T存储以类型为 key所有类型共享一个字典。public class DictionaryCache { private static DictionaryType, string _cache new DictionaryType, string(); public static string GetCacheT() { Type type typeof(T); if (!_cache.ContainsKey(type)) _cache[type] ${typeof(T).FullName}_{DateTime.Now:yyyyMMddHHmmss.fff}; return _cache[type]; } }缺点每次调用都要查字典(哈希查找)多线程下需要加锁。泛型缓存利用泛型类的特性——每种类型参数对应一个独立的类副本静态字段也是独立的。public class GenericCacheT { private static readonly string _typeTime; static GenericCache() { // 每种 T 的静态构造函数只执行一次CLR 保证线程安全 _typeTime ${typeof(T).FullName}_{DateTime.Now:yyyyMMddHHmmss.fff}; } public static string GetCache() _typeTime; } // GenericCacheint 和 GenericCachestring 是完全不同的类各有独立的静态字段 GenericCacheint.GetCache(); GenericCachestring.GetCache();优点直接访问静态字段无需字典查找性能更高CLR 保证静态构造函数线程安全无需手动加锁。实际应用在手写 ORM 框架时用泛型缓存存储实体类的反射信息(属性列表、表名等)避免每次操作都重复反射大幅提升性能。Q10泛型方法调用时什么情况下可以省略类型参数出题意图考察对编译器类型推断的理解。答当编译器能从传入的参数推断出类型参数时可以省略尖括号public static void ShowT(T value) { } Showint(123); // 显式指定 Show(123); // 省略编译器推断 T int Show(hello); // 省略编译器推断 T string如果类型参数只出现在返回值中无法从参数推断则必须显式指定public static T CreateT() where T : new() new T(); var obj CreateMyClass(); // 必须指定无法推断Q11泛型类的子类继承有哪些方式出题意图考察对泛型继承的理解实际项目中经常遇到。答public abstract class GenericBaseT { public void Show(T t) { } } // 方式一子类固定类型参数子类本身不是泛型类 public class ChildA : GenericBaseint { } // 方式二子类继续保持泛型把类型参数传递给父类 public class ChildBS : GenericBaseS { }两种方式各有适用场景固定类型适合具体业务类(如UserRepository : RepositoryUser)保持泛型适合通用基础设施类(如通用仓储基类)。Q12default(T)有什么用出题意图考察在泛型方法中处理默认值的能力。答在泛型方法中无法直接写return null或return 0因为 T 可能是值类型也可能是引用类型。default(T)返回 T 的默认值引用类型返回null值类型返回对应的零值(int→0bool→falseDateTime→0001-01-01)public T GetDefaultT() { return default(T); // C# 7.1 之后可简写为 default }综合应用题设计通用仓储题目设计一个通用仓储接口和实现类要求实体必须有Id属性、必须是引用类型、支持基本 CRUD、用泛型缓存优化反射性能。出题意图综合考察泛型约束、泛型缓存、接口设计是中高级面试常见的大题形式。答// 实体接口 public interface IEntity { int Id { get; set; } } // 泛型缓存缓存实体类型的反射信息每种类型只反射一次 public class EntityCacheT where T : class { private static readonly string _tableName; private static readonly PropertyInfo[] _properties; static EntityCache() { _tableName typeof(T).Name; _properties typeof(T).GetProperties(); } public static string TableName _tableName; public static PropertyInfo[] Properties _properties; } // 仓储接口 public interface IRepositoryT where T : class, IEntity, new() { T GetById(int id); IEnumerableT GetAll(); void Add(T entity); void Delete(int id); } // 仓储实现 public class RepositoryT : IRepositoryT where T : class, IEntity, new() { private readonly ListT _store new(); public T GetById(int id) { Console.WriteLine($查询表: {EntityCacheT.TableName}); return _store.FirstOrDefault(e e.Id id); } public IEnumerableT GetAll() _store; public void Add(T entity) { entity.Id _store.Count 1; _store.Add(entity); } public void Delete(int id) { var entity GetById(id); if (entity ! null) _store.Remove(entity); } } // 实体类 public class User : IEntity { public int Id { get; set; } public string Name { get; set; } } // 使用 var repo new RepositoryUser(); repo.Add(new User { Name 张三 }); var user repo.GetById(1);设计要点where T : class, IEntity, new()三重约束保证类型安全泛型缓存避免重复反射每种实体类型只初始化一次接口与实现分离遵循依赖倒置原则
01-C#.Net-泛型-面试题
Q1什么是泛型为什么要用泛型出题意图考察对泛型基本概念的理解以及是否能说清楚它解决了什么问题。答泛型是一种延迟声明类型的机制。声明时用占位符T代替具体类型调用时再指定。引入泛型主要解决两个问题性能问题用object作为通用参数时值类型会发生装箱/拆箱有额外的堆分配和 CPU 开销。泛型在 CLR 层面对值类型生成专用代码完全避免装箱性能等同于专用方法。类型安全问题object参数在编译期无法检查类型强制转换错误只能在运行时暴露。泛型在编译期就能检查类型错误提前发现。// 泛型方法一个方法任意类型无装箱编译期安全 public static void ShowT(T value) { Console.WriteLine(${typeof(T).Name}: {value}); } Showint(123); // 显式指定 Show(hello); // 类型推导省略尖括号Q2泛型是语法糖吗底层原理是什么出题意图区分候选人是否真正理解泛型的实现机制还是只会用。答泛型不是语法糖。语法糖是编译器提供的便捷写法编译后转换成等价的普通代码。泛型是由CLR 在底层真正支持的从 .NET Framework 2.0 开始引入编译器和运行时都必须支持。底层机制编译器将泛型类/方法编译成带占位符的 IL 代码例如List\1[T]、Dictionary2[TKey,TValue]JIT 编译时值类型(int、struct等)CLR 为每种类型生成一份独立的本地代码不存在装箱引用类型CLR 共享同一份本地代码(引用类型内存布局相同都是指针大小)这就是为什么泛型方法性能等同于专用方法而远优于object方法。Q3泛型方法的性能为什么优于 object 参数方法出题意图考察对装箱拆箱机制的理解以及泛型性能优势的具体原因。答核心原因是装箱拆箱。值类型(如int)传入object参数时需要在堆上分配新对象(装箱)取出时再拆箱有额外的内存分配和 GC 压力。泛型方法对值类型生成专用代码直接操作无需装箱。int value 123; // Object方式每次调用都装箱 ShowObject(value); // int → object(堆分配)→ int // 泛型方式无装箱直接操作 int Showint(value);性能差异在大量循环 值类型场景下最明显例如 1 亿次调用object方法耗时可能是泛型方法的数倍。Q4泛型约束有哪些分别有什么作用出题意图考察泛型约束的掌握程度这是实际开发中经常用到的。答约束语法作用基类约束where T : PeopleT 必须是 People 或其子类方法内可访问 People 的成员接口约束where T : ISportsT 必须实现该接口方法内可调用接口方法引用类型约束where T : classT 只能是引用类型值类型约束where T : structT 只能是值类型且可以直接new T()无参构造约束where T : new()T 必须有无参构造函数方法内可new T()枚举约束where T : EnumT 必须是枚举类型父子关系约束where T : ST 必须是 S 或 S 的子类多个约束可以叠加// T 必须是引用类型、实现 IEntity 接口、有无参构造函数 public class RepositoryT where T : class, IEntity, new() { public T Create() new T(); }解答思路回答时结合实际场景比如基类约束让我在泛型方法里能访问特定属性避免了强制转换new()约束让我可以在方法内部创建实例。Q5以下代码有什么问题如何用泛型约束解决public class RepositoryT { public T Create() { return new T(); // 编译错误 } public void Save(T entity) { Console.WriteLine(entity.Id); // 编译错误 } }出题意图考察泛型约束的实际应用以及如何通过约束解决编译时类型安全问题。答两个错误原因new T()编译器不知道 T 是否有无参构造函数entity.Id编译器不知道 T 是否有Id属性解决方案public interface IEntity { int Id { get; set; } } // 加上约束T 必须是引用类型、实现 IEntity、有无参构造函数 public class RepositoryT where T : class, IEntity, new() { public T Create() { return new T(); // ✓ } public void Save(T entity) { Console.WriteLine(entity.Id); // ✓ 可以访问 Id } }解答思路识别编译错误的根本原因 → 选择对应约束 → 理解约束组合使用。Q6ListAnimal和ListCat有继承关系吗为什么出题意图考察对泛型类型系统的理解引出协变逆变话题。答没有继承关系。即使Cat继承自AnimalListCat和ListAnimal也是完全独立的两个类型ListAnimal list new ListCat(); // 编译报错原因泛型类用不同类型参数实例化后得到的是不同的类型彼此没有任何关系。这是 C# 类型系统的设计目的是保证类型安全——如果允许上面的赋值就可以往list里Add(new Dog())但实际底层是ListCat运行时会崩溃。如果需要这种灵活性应该使用协变(IEnumerableout T)。Q7什么是协变和逆变请解释以下代码的编译结果。// 片段1 ListAnimal list1 new ListCat(); // ? // 片段2 IEnumerableAnimal list2 new ListCat(); // ? // 片段3 public interface IReaderout T { T Read(); } IReaderAnimal reader new ReaderCat(); // ? // 片段4 public interface IWriterin T { void Write(T item); } IWriterCat writer new WriterAnimal(); // ?出题意图这是中高级 .NET 面试的高频考点考察对协变逆变的真实理解。答片段1编译错误❌ —ListT不支持协变片段2编译成功✓ —IEnumerableout T支持协变片段3编译成功✓ —out T协变右边子类左边父类片段4编译成功✓ —in T逆变右边父类左边子类协变(out)T 只能作为返回值允许右边用子类左边用父类。public interface ICustomerListOutout T { T Get(); // ✓ 可以作为返回值 // void Show(T t); // ✗ 不能作为参数 } ICustomerListOutAnimal list new CustomerListOutCat(); // ✓逆变(in)T 只能作为参数允许右边用父类左边用子类。public interface ICustomerListInin T { void Show(T t); // ✓ 可以作为参数 // T Get(); // ✗ 不能作为返回值 } ICustomerListInCat list new CustomerListInAnimal(); // ✓记忆口诀out协变出去的(返回值)子类 → 父类出口放宽in逆变进来的(参数)父类 → 子类入口放宽注意协变逆变只适用于泛型接口和泛型委托不适用于泛型类。Q8协变逆变为什么只适用于接口和委托不适用于泛型类出题意图考察对协变逆变设计原理的深层理解区分中高级候选人。答因为泛型类的类型参数既可以出现在参数位置也可以出现在返回值位置无法同时满足协变和逆变的安全约束。以ListT为例它既有Add(T item)(T 做参数)又有T this[int index](T 做返回值)。如果允许协变ListAnimal list new ListCat(); // 假设合法 list.Add(new Dog()); // 往 ListCat 里加了一只 Dog类型系统崩溃而接口可以通过in/out约束明确规定 T 只出现在参数或返回值的某一侧编译器在定义接口时就能验证安全性。IEnumerableout T只有GetEnumerator()(T 只做返回值)所以可以协变。IListT因为有Add(T)和T this[int]所以不支持协变。Q9泛型缓存和字典缓存有什么区别出题意图考察对泛型静态成员特性的理解以及实际应用能力(ORM、框架开发场景)。答字典缓存用一个静态DictionaryType, T存储以类型为 key所有类型共享一个字典。public class DictionaryCache { private static DictionaryType, string _cache new DictionaryType, string(); public static string GetCacheT() { Type type typeof(T); if (!_cache.ContainsKey(type)) _cache[type] ${typeof(T).FullName}_{DateTime.Now:yyyyMMddHHmmss.fff}; return _cache[type]; } }缺点每次调用都要查字典(哈希查找)多线程下需要加锁。泛型缓存利用泛型类的特性——每种类型参数对应一个独立的类副本静态字段也是独立的。public class GenericCacheT { private static readonly string _typeTime; static GenericCache() { // 每种 T 的静态构造函数只执行一次CLR 保证线程安全 _typeTime ${typeof(T).FullName}_{DateTime.Now:yyyyMMddHHmmss.fff}; } public static string GetCache() _typeTime; } // GenericCacheint 和 GenericCachestring 是完全不同的类各有独立的静态字段 GenericCacheint.GetCache(); GenericCachestring.GetCache();优点直接访问静态字段无需字典查找性能更高CLR 保证静态构造函数线程安全无需手动加锁。实际应用在手写 ORM 框架时用泛型缓存存储实体类的反射信息(属性列表、表名等)避免每次操作都重复反射大幅提升性能。Q10泛型方法调用时什么情况下可以省略类型参数出题意图考察对编译器类型推断的理解。答当编译器能从传入的参数推断出类型参数时可以省略尖括号public static void ShowT(T value) { } Showint(123); // 显式指定 Show(123); // 省略编译器推断 T int Show(hello); // 省略编译器推断 T string如果类型参数只出现在返回值中无法从参数推断则必须显式指定public static T CreateT() where T : new() new T(); var obj CreateMyClass(); // 必须指定无法推断Q11泛型类的子类继承有哪些方式出题意图考察对泛型继承的理解实际项目中经常遇到。答public abstract class GenericBaseT { public void Show(T t) { } } // 方式一子类固定类型参数子类本身不是泛型类 public class ChildA : GenericBaseint { } // 方式二子类继续保持泛型把类型参数传递给父类 public class ChildBS : GenericBaseS { }两种方式各有适用场景固定类型适合具体业务类(如UserRepository : RepositoryUser)保持泛型适合通用基础设施类(如通用仓储基类)。Q12default(T)有什么用出题意图考察在泛型方法中处理默认值的能力。答在泛型方法中无法直接写return null或return 0因为 T 可能是值类型也可能是引用类型。default(T)返回 T 的默认值引用类型返回null值类型返回对应的零值(int→0bool→falseDateTime→0001-01-01)public T GetDefaultT() { return default(T); // C# 7.1 之后可简写为 default }综合应用题设计通用仓储题目设计一个通用仓储接口和实现类要求实体必须有Id属性、必须是引用类型、支持基本 CRUD、用泛型缓存优化反射性能。出题意图综合考察泛型约束、泛型缓存、接口设计是中高级面试常见的大题形式。答// 实体接口 public interface IEntity { int Id { get; set; } } // 泛型缓存缓存实体类型的反射信息每种类型只反射一次 public class EntityCacheT where T : class { private static readonly string _tableName; private static readonly PropertyInfo[] _properties; static EntityCache() { _tableName typeof(T).Name; _properties typeof(T).GetProperties(); } public static string TableName _tableName; public static PropertyInfo[] Properties _properties; } // 仓储接口 public interface IRepositoryT where T : class, IEntity, new() { T GetById(int id); IEnumerableT GetAll(); void Add(T entity); void Delete(int id); } // 仓储实现 public class RepositoryT : IRepositoryT where T : class, IEntity, new() { private readonly ListT _store new(); public T GetById(int id) { Console.WriteLine($查询表: {EntityCacheT.TableName}); return _store.FirstOrDefault(e e.Id id); } public IEnumerableT GetAll() _store; public void Add(T entity) { entity.Id _store.Count 1; _store.Add(entity); } public void Delete(int id) { var entity GetById(id); if (entity ! null) _store.Remove(entity); } } // 实体类 public class User : IEntity { public int Id { get; set; } public string Name { get; set; } } // 使用 var repo new RepositoryUser(); repo.Add(new User { Name 张三 }); var user repo.GetById(1);设计要点where T : class, IEntity, new()三重约束保证类型安全泛型缓存避免重复反射每种实体类型只初始化一次接口与实现分离遵循依赖倒置原则