More-Effective-CSharp-1.1使用 1.x 框架 API 的泛型版本

.NET 平台的头两个版本 (1.1 及 1.2) 不支持泛型

System.Object 是所有类型的最终基类[1]

为何要使用泛型代码?

  1. 健壮性

将 Object 作为 参数或返回类型 难免会出现意外的类型 导致运行时的错误[2]

  1. 性能

    1.1 版本的弱类型系统需要在代码中添加检验代码以保证参数或返回类型的正确[3]

    ,当检验失败时还会执行更多的其他代码,这不可避免的导致了更大的性能开销[4]

“总体说来,弱类型系统将带来各种各样的麻烦,从性能低下直至程序异常终止等。”

自 .NET 2.0 引入了泛型[2:1]

以 System.IComparable 为例比较泛型版本的优势[5]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface IComparable<T>
{
int CompareTo(T other);
}

// 1.1版本
public interface IComparable
{
int CompareTo(object obj);
}

// 实现
public int CompareTo(Customer right)
{
return Name.CompareTo(right.Name);
}

public int CompareTo(object right)
{
if (!(right is Customer))
throw new ArgumentException("Argument not a customer","right");
Customer rightCustomer = (Customer)right;
return Name.CompareTo(rightCustomer.Name);
}

使用泛型接口的四个优势

  1. 简洁
  2. 高效
  3. 避免了所有的装箱/拆箱以及类型转换操作 (个人认为这点其实包含在1与2中)[4:1]
  4. 不会抛出异常 (非泛型版本中运行时可能出现的异常变为可由编辑器捕获的异常[2:2]

个人总结: 用基类传来传去固然是不怎么安全,并且还会增加代码量与更多性能开销,一般情况下用泛型约束确实是最好的选择 (参数不继承自同一基类另说)[6]


  1. Microsoft Learn, C# language specification - Types。用于支撑 C# 统一类型系统中所有类型最终可视为 object 的语言层事实。 ↩︎

  2. Microsoft Learn, Generic types and methods。用于支撑泛型把类型事实参数化、由编译器维护类型安全,并能让类型错误更早暴露的判断。 ↩︎ ↩︎ ↩︎

  3. Microsoft Learn, IComparable Interface。用于支撑非泛型 IComparable.CompareTo 接收 object,因此实现中常需要运行时类型检查的边界。 ↩︎

  4. Microsoft Learn, Boxing and UnboxingGenerics in the runtime。用于支撑值类型进入 object API 时会装箱、泛型运行时可避免常见装箱路径,从而减少额外分配和转换成本。 ↩︎ ↩︎

  5. Microsoft Learn, IComparable InterfaceIComparable.CompareTo(T) Method。用于支撑泛型比较接口把比较对象类型写进签名,减少非泛型 object 入口的类型转换和检查。 ↩︎

  6. Microsoft Learn, Constraints on type parameters。用于支撑泛型约束可以声明类型参数必须具备的能力,让编译器允许调用相应成员并检查调用方传入类型。 ↩︎