一些同学对泛型不是很了解,只知道使用,不知道为什么使用,有什么好处等;
下面我来谈谈自己对泛型的理解。
泛型就是把类型参数化也叫参数化类型。通俗来讲泛型就是“通用类型”,它可以代替任何的数据类型,使类型参数化,从而达到只实现一个方法就可以操作多种数据类型的目的。
原理:延时声明,在运行时进行编译。
微软官方定义:泛型是为所存储或使用的一个或多个类型具有占位符的类、结构、接口和方法。 泛型集合类可以将类型形参用作其存储的对象类型的占位符;类型形参呈现为其字段的类型和其方法的参数类型。
泛型将类型参数的概念引入 .NET,这样就可在设计类和方法时,不必确定一个或多个具体参数,它的具体参数可延迟到客户端代码中声明、实现。
通过使用泛型类型参数 T,这意味着使用泛型的类型参数T,可以多种形式调用,运行时类型转换避免了装箱操作的代价和风险。
如下所示:
// 声明一个泛型类,T是类型占位符.
public class GenericList<T>
{
public void Add(T input) { }
}
class TestGenericList
{
private class ExampleClass { }
static void Main()
{
// 声明一个int类型的集合.
GenericList<int> list1 = new GenericList<int>();
list1.Add(1);
// 声明一个string类型的集合.
GenericList<string> list2 = new GenericList<string>();
list2.Add("");
// 声明一个对象的类型集合.
GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
list3.Add(new ExampleClass());
}
}
类型安全。泛型将类型检查转移到编译器。 没有必要编写代码来测试正确的数据类型,因为它会在编译时强制执行。 降低了强制类型转换的必要性和运行时错误的可能性。
代码更少,代码重用。例如:泛型List< T >。
性能更好。无须装箱和取消装箱的操作,节约时间。
在 .NET 中,开发人员随时会使用泛型,有时隐式使用,有时显式使用。 在 .NET 中使用 LINQ 时,你是否曾经注意到,使用的正是 IEnumerable< T >? 或者,你是否曾经看到过有关使用实体框架来与数据库通信的“泛型存储库”在线示例,其中的大多数方法返回 IQueryable< T >? 你可能很想知道,这些示例中的 T 是什么意思,为什么要使用它?
泛型在 .NET Framework 2.0 中首次引入,它本质上是一个“代码模板”,可让开发者定义类型安全数据结构,无需处理实际数据类型。 例如,List< T > 是一个可以声明的泛型集合,可与 List< int >、List< string > 或 List< Person > 等任何类型结合使用。
为方便理解泛型的作用,让我们看看添加泛型之前和之后的一个特定类:ArrayList。 在 .NET Framework 1.0 中,ArrayList 元素属于 Object 类型。 添加到集合的任何元素都会以静默方式转换为 Object。 从列表读取元素时,会发生相同的情况。 此过程称为装箱和取消装箱,它会影响性能。 但除了性能之外,在编译时无法确定列表中的数据的类型,这会形成一些脆弱的代码。 泛型解决了此问题,它可以定义每个列表实例将要包含的数据类型。 例如,只能将整数添加到 List,只能将人员添加到 List。
泛型还可以在运行时使用。 运行时知道你要使用的数据结构类型,并可以更高效地将数据结构存储在内存中。
示例:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
namespace GenericsExample {
class Program {
static void Main(string[] args) {
//泛型集合
List<int> ListGeneric = new List<int> { 5, 9, 1, 4 };
//非泛型集合
ArrayList ListNonGeneric = new ArrayList { 5, 9, 1, 4 };
// 为泛型集合排序开启定时器
Stopwatch s = Stopwatch.StartNew();
ListGeneric.Sort();
s.Stop();
Console.WriteLine($"泛型排序: {ListGeneric} \n 花费时间: {s.Elapsed.TotalMilliseconds}ms");
//为非泛型集合排序开启定时器
Stopwatch s2 = Stopwatch.StartNew();
ListNonGeneric.Sort();
s2.Stop();
Console.WriteLine($"非泛型排序: {ListNonGeneric} \n 花费时间: {s2.Elapsed.TotalMilliseconds}ms");
Console.ReadLine();
}
}
}
输出结果:
泛型排序: System.Collections.Generic.List`1[System.Int32]
花费时间: 0.0034ms
非泛型排序: System.Collections.ArrayList
花费时间: 0.2592ms
在此处可以看到的第一个优点是,泛型列表的排序比非泛型列表要快得多。 还可以看到,泛型列表的类型是不同的 ([System.Int32]),而非泛型列表的类型已通用化。 由于运行时知道泛型 List 的类型是 Int32,因此可以将列表元素存储在内存中的基础整数数组内;而非泛型 ArrayList 必须将每个列表元素强制转换为对象。 如本示例中所示,多余的强制转换会占用时间,降低列表排序的速度。
约束告知编译器类型参数必须具备的功能。 在没有任何约束的情况下,类型参数可以是任何类型。 编译器只能假定 System.Object 的成员,它是任何 .NET 类型的最终基类。如果客户端代码使用不满足约束的类型,编译器将发出错误。 通过使用 where 上下文关键字指定约束。 下表列出了五种类型的约束:
编号 | 约束 | 描述 |
---|---|---|
1 | where T : struct | 类型参数必须是不可为null的值类型。由于所有值类型都具有可访问的无参构造函数,因此struct约束表示new() 约束,并且不能与new() 约束结合使用。 |
2 | where T : class | 类型参数必须是引用类型。此约束还应用于任何类、接口、委托或数组类型。 |
3 | where T : new() | 类型参数必须具有公共无参构造函数。 |
4 | where T : <基类名> | 类型参数必须是指定的基类或派生自指定的基类。 |
5 | where T : <接口名称> | 类型参数必须是指定的接口或实现指定的接口。 |