面试题答案
一键面试编译器生成IL代码方式
- 类型擦除:C# 不像 Java 采用类型擦除。在编译时,C# 编译器会为每个唯一的泛型类型实例化生成单独的 IL 代码。例如,对于
List<int>
和List<string>
,编译器会生成两份不同的 IL 代码,分别对应这两种具体类型的List
。 - 泛型类型参数:对于泛型类型定义(如
class MyGenericClass<T>
),编译器会生成一个占位符类型参数T
。在实例化时,用实际类型替换T
生成具体的 IL 代码。例如,MyGenericClass<int>
的实例化会生成使用int
替换T
后的具体 IL 代码。 - 泛型方法:类似地,对于泛型方法(如
void MyGenericMethod<T>(T param)
),编译器生成的 IL 代码中方法参数和返回值等使用占位符类型T
。在调用泛型方法时,如果传入的类型不同,编译器会为每种不同的调用类型生成特定的 IL 代码。
不同场景异同
相同点
- 占位符机制:在泛型类型和泛型方法的定义阶段,都使用占位符类型参数(如
T
)来表示未确定的类型。 - 实例化生成:在实例化泛型类型或调用泛型方法时,编译器都会根据实际传入的类型生成具体的 IL 代码。
不同点
- 定义方式:泛型类型是整个类基于类型参数定义,而泛型方法是在类中基于类型参数定义的单个方法。这导致在生成 IL 代码时,泛型类型生成的是整个类结构相关的代码,而泛型方法生成的只是单个方法的代码。
- 实例化时机:泛型类型实例化是创建对象时,而泛型方法实例化是在调用方法时。例如
MyGenericClass<int> obj = new MyGenericClass<int>();
实例化泛型类型,而obj.MyGenericMethod<string>("test");
实例化泛型方法。
编译器优化提升性能
- 减少装箱拆箱:尽量使用值类型作为泛型类型参数。因为使用引用类型可能导致不必要的装箱拆箱操作。例如,
List<int>
比List<object>
性能更好,当List<object>
存储值类型时会发生装箱。 - 约束类型参数:合理使用类型约束(如
where T : struct
,where T : class
,where T : new()
等)。编译器可以基于这些约束进行更好的优化。例如,当有where T : IComparable
约束时,编译器知道T
类型一定实现了IComparable
接口,从而在生成代码时可以更高效地调用比较方法。 - 复用泛型实例:对于无状态的泛型类型,尽量复用实例。例如,
Comparer<T>
类,如果创建多个相同类型的Comparer<T>
实例,会造成资源浪费,应使用Comparer<T>.Default
复用已有的实例。 - 避免不必要的泛型嵌套:过多的泛型嵌套会增加编译器生成代码的复杂性和开销。例如,
List<List<T>>
比简单的List<T>
更复杂,尽量简化泛型结构。