开发人员在 .NET 中一直使用泛型,无论是隐式还是显式。 在 .NET 中使用 LINQ 时,你是否曾经注意到,使用的正是 IEnumerable<T>? 或者,如果曾经看到使用 Entity Framework 与数据库通信的“泛型存储库”联机示例,你是否看到大多数方法返回 IQueryable<T>
? 你可能想知道这些示例中的 T 是什么,为什么它在那里。
泛型最初在 .NET Framework 2.0 中引入,实质上是一个“代码模板”,开发人员无需提交实际数据类型即可定义 类型安全的 数据结构。 例如,是一个List<T>,可以声明并用于任何类型,例如List<int>
,List<string>
或List<Person>
。
为了了解泛型为何有用,让我们来看看添加泛型之前和之后的特定类: ArrayList 在 .NET Framework 1.0 中, ArrayList
元素的类型 Object为 。 添加到集合的任何元素都以无提示方式转换为一个 Object
。 从列表中读取元素时也会出现相同的情况。 此过程称为 装箱和取消装箱,会影响性能。 但是,除了性能之外,在编译时无法确定列表中的数据类型,这会使某些代码变得脆弱。 泛型通过定义列表的每个实例将包含的数据类型来解决此问题。 例如,只能向List<int>
添加整数,并且只能将人员添加到List<Person>
。
泛型还可以在运行时使用。 运行时知道所使用的数据结构类型,并且可以更有效地将其存储在内存中。
以下示例是一个小程序,演示了在运行时了解数据结构类型的效率:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace GenericsExample {
class Program {
static void Main(string[] args) {
//generic list
List<int> ListGeneric = new List<int> { 5, 9, 1, 4 };
//non-generic list
ArrayList ListNonGeneric = new ArrayList { 5, 9, 1, 4 };
// timer for generic list sort
Stopwatch s = Stopwatch.StartNew();
ListGeneric.Sort();
s.Stop();
Console.WriteLine($"Generic Sort: {ListGeneric} \n Time taken: {s.Elapsed.TotalMilliseconds}ms");
//timer for non-generic list sort
Stopwatch s2 = Stopwatch.StartNew();
ListNonGeneric.Sort();
s2.Stop();
Console.WriteLine($"Non-Generic Sort: {ListNonGeneric} \n Time taken: {s2.Elapsed.TotalMilliseconds}ms");
Console.ReadLine();
}
}
}
此程序生成类似于以下内容的输出:
Generic Sort: System.Collections.Generic.List`1[System.Int32]
Time taken: 0.0034ms
Non-Generic Sort: System.Collections.ArrayList
Time taken: 0.2592ms
此处可以注意到的第一件事是,对泛型列表进行排序比对非泛型列表进行排序要快得多。 你可能还会注意到泛型列表的类型是不同的([System.Int32]),而非泛型列表的类型是通用的。 由于运行时知道泛型 List<int>
的类型是 Int32,因此它可以将列表元素存储在内存中的一个基础整数数组中,而非泛型 ArrayList
则必须将每个列表元素转换为对象类型。 如本示例中所示,多余的强制转换会占用时间,降低列表排序的速度。
运行时了解泛型类型的另一个优势是更好的调试体验。 在 C# 中调试泛型时,你知道每个元素在数据结构中的类型。 如果没有泛型,则不知道每个元素的类型。