C#笔记——3.泛型
泛型简介:
C#2.0泛型机制的引入,实现了类和方法的类型参数化,即类型成为参数实现逻辑复用,将大量的类型安全检查从运行时转移到了编译时,提高了代码运行速度。C#语言提供的泛型机制主要分为两种形式:
泛型类型(包括:泛型类、泛型接口、泛型委托以及泛型结构)以及泛型方法。
在程序中使用泛型时,C#编译器、JIT编译器(Just-In-Time compiler 即时编译器)以及CLR(Common Language Runtime 公共语言运行时)会发生相应的变化:
- C#编译器会根据我们所写的C#代码为泛型类型创建出IL代码(Microsoft Intermediate Language)
- JIT编译器会将这份IL定义与一系列类型参数结合起来,创建出封闭的closed泛型类型
- 公共语言运行时CLR则在运行时为二者提供支持
对于非泛型的类型来说,C#编译器创建出的IL形式的类定义与JIT编译器创建出的机器码之间是一一对应的关系,对于泛型类而言,JIT编译器会判断类型参数,并据此生成特定的指令,通过各种优化技术把不同的类型参数合并成同一份机器码
当泛型类的类型参数是引用类型时,不同的类型参数在运行时执行的是同一套机器码,但是如果至少有一个类型参数是值类型时,那么JIT编译器会根据不同的类型参数生成对应版本的机器码
泛型的优点:
- 类型安全
当我们使用泛型类型或者泛型方法来操作一个具体的数据类型时,编译器会保证这些泛型类型或者泛型方法只适用于与该数据类型兼容的对象,否则,编译器将会报错。
- 提升性能
如果没有泛型机制,我们则需要使用object类型来作为参数或者返回值类型,这样则需要进行强制的类型转换;当我们操作的对象是值类型时,又将会需要装箱与拆箱的操作。引入泛型机制之后,我们通过创建泛型类型或者泛型方法来操作类型,则无需进行强制类型转换,运行时便无需进行类型安全检查,操作值类型时也无需进行装箱、拆箱,提升了代码性能
- 二进制代码的重用
使用泛型可以更好地重用二进制代码,泛型类可以定义一次后使用多种不同的类型来实例化,而不需要像C++模板那样访问源代码。泛型类型可以在一种类型中定义后再在其他.Net语言中使用。
- 代码的扩展
泛型类的定义会放在程序集中,在用特定类型实例化泛型类时不会在IL代码中复制这些类。JIT编译器在把泛型类编译为本地代码时,会给每个值类型创建一个新类;而引用类型则共享同一个本地类的所有相同的实现代码。
泛型类型
创建一个泛型类型
首先看一个简化的非泛型链表类
链表节点类:LinkedListNode,包含一个属性Value,以及对链表中上一个、下一个元素的引用,可以通过属性来进行访问。
public class LinkedListNode
{
public object Value { get; private set; }
public LinkedListNode Next { get; internal set; }
public LinkedListNode Prev { get; internal set; }
public LinkedListNode(object value) {
this.Value = value;
}
}
链表类:LinkedList,包含LinkedListNode类型的First、Last属性,来标记链表的头和尾,一个AddLast()方法来在链表尾部添加一个新元素;实现GetEnumerator()方法使链表可以使用foreach来遍历。
public class LinkedList : IEnumerable
{
public LinkedListNode First { get; private set; }
public LinkedListNode Last { get; private set; }
public LinkedListNode AddLast(object node) {
var newNode = new LinkedListNode(node);
if (First == null)
{
First = newNode;
Last = First;
}
else {
LinkedListNode previous = Last;
Last.Next = newNode;
Last = newNode;
Last.Prev = previous;
}
return newNode;
}
public IEnumerator GetEnumerator()
{
LinkedListNode current = First;
while (current != null) {
yield return current.Value;
current = current.Next;
}
}
}
客户端测试代码:
static void Main(string[] args)
{
var TheList = new LinkedList();
TheList.AddLast(2);
TheList.AddLast(4);
TheList.AddLast(8);
foreach (int i in TheList) {
Console.WriteLine(i);
}
Console.ReadKey();
}
该非泛型链表类的泛型版本为:
LinkedListNode< T >类,将LinkedListNode 类用一个泛型类型T声明为LinkedListNode< T >,其属性Value是T类型,不再是object类型;Next以及Prev属性的类型和节点类保持一致是LinkedListNode< T >类型;其构造函数也变为接收T类型的对象。
public class LinkedListNode<T>
{
public T Value { get; private set; }
public LinkedListNode<T> Next { get; internal set; }
public LinkedListNode<T> Prev { get; internal set; }
public LinkedListNode(T value) {
this.Value = value;
}
}
LinkedList< T >类,相应的包含LinkedListNode< T >类型的First、Last属性,来标记链表的头和尾,一个AddLast(T node)泛型方法来在链表尾部添加一个新的LinkedListNode< T >类型的元素;实现IEnumerable< T >泛型接口。
public class LinkedList<T> : IEnumerable<T>
{
public LinkedListNode<T> First { get; private set; }
public LinkedListNode<T> Last { get; private set; }
public LinkedListNode<T> AddLast(T node) {
var newNode = new LinkedListNode<T>(node);
if (First == null)
{
First = newNode;
Last = First;
}
else {
LinkedListNode<T> previous = Last;
Last.Next = newNode;
Last = newNode;
Last.Prev = previous;
}
return newNode;
}
public IEnumerator<T> GetEnumerator()
{
LinkedListNode<T> current = First;
while (current != null) {
yield return current.Value;
current = current.Next;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
客户端测试代码:
static void Main(string[] args)
{
var TheList = new LinkedList<int>();
TheList.AddLast(2);
TheList.AddLast(4);
TheList.AddLast(8);
foreach (int i in TheList)
{
Console.WriteLine(i);
}
Console.ReadKey();
}
泛型接口
泛型接口可防止在使用非泛型接口操作值类型时可能引起的装箱操作,并且提供编译时的类型安全。
泛型委托
泛型结构
泛型方法
在BubbleSorter类中写一个冒泡排序的算法:
class BubbleSorter
{
public static void Sort(int[] sortArray) {
bool swap = true;
do {
swap = false;
for (int i = 0; i < sortArray.Length -1 ; i++) {
if (sortArray[i] > sortArray[i+1]) {
int temp = sortArray[i];
sortArray[i] = sortArray[i + 1];
sortArray[i + 1] = temp;
swap = true;
}
}
} while (swap);
}
}
当前的冒泡排序只适用于int类型的数组,当我们想要让这个冒泡排序算法可以给任何对象排序时,可以使用泛型来解决:
class BubbleSorter
{
public static void Sort< T >(IList< T > sortArray,Func< T,T,bool > comparison) {
bool swap = true;
do {
swap = false;
for (int i = 0; i < sortArray.Count - 1; i++) {
if (comparison(sortArray[i],sortArray[i+1])) {
T temp = sortArray[i];
sortArray[i] = sortArray[i+1];
sortArray[i + 1] = temp;
swap = true;
}
}
} while (swap);
}
}
测试类型Employee:
class Employee
{
public string Name { get; set; }
public int Salary { get; set; }
public Employee(string name , int salary){
this.Name = name;
this.Salary = salary;
}
public static bool Comparison(Employee e1,Employee e2) {
return e1.Salary > e2.Salary;
}
public static void print(Employee[] eArray) {
for (int i =0; i < eArray.Length; i++) {
Console.WriteLine(eArray[i].Name);
}
}
}
客户端代码:
static void Main(string[] args)
{
Employee[] eArray = {
new Employee("ding",7000),
new Employee("jun",9000),
new Employee("xiang",6000),
new Employee("liang",8000)
};
Employee.print(eArray);
Console.WriteLine("------After Sort-------");
BubbleSorter.Sort(eArray,Employee.Comparison);
Employee.print(eArray);
Console.ReadKey();
}
类型约束和类型推断
类型约束
类型约束(type constraint),在创建自定义的泛型类型和方法时,我们可以制定规则从而判断哪些是泛型类型或泛型方法能接受的有效类型实参。只有理解了类型约束,在以后工作中使用框架时才知道可以使用哪些选项。
有4种约束可供使用,它们的常规语法相同。约束要放到泛型方法或泛型类型声明的末尾,并由上下文关键字where来引入。
约束可以规范的分为:主要约束、次要约束和构造函数约束。
主要约束包括:引用类型约束、值类型约束、使用类的转换类型约束
次要约束包括:使用接口或者其他类型参数的转换类型约束(参数类型约束)
构造函数约束。
其中,主要约束是可选的,但是只能有一个;次要约束则可以有多个;构造函数约束也是可选的(如果已经约束为值类型,则不能再添加构造函数约束)
- 引用类型约束
引用类型约束用于确保使用的类型实参是引用类型(它表示成T : class,且必须是为类型参数指定的第一个约束)。类型实参任何类、接口、数组、委托,或者已知是引用类型的另一个类型参数。
struct RefSample< T > where T : class
上面代码我们声明了一个泛型结构,并且约束它的类型参数必须为引用类型
即RefSample< string > 合法 、 RefSample< int > 不合法
- 值类型约束
值类型约束表示成T : struct,确保使用的类型实参是值类型,包括枚举(enums)。它将可空类型排除在外。
class ValSample< T > where T : struct
上面代码我们声明了一个泛型类,并且约束它的类型参数必须为值类型
即ValSample< int > 合法、 ValSample< string > 不合法
- 构造函数类型约束
构造函数类型约束表示成T : new(),必须写成类型参数的最后一个约束,它检查类型实参是否有一个可用于创建类型实例的无参构造函数。这适用于所有值类型、所有没有显式声明构造函数的非静态、非抽象类、所有显式声明了一个公共无参构造函数的非抽象类。
public T CreateInstance< T >() where T : new(){
return new T();
}
上面代码我们声明了一个泛型方法,其类型参数必须有一个无参数的构造函数,且该方法返回一个该类型参数类型的一个实例。
即CreateInstance< int >() 以及CreateInstance< object > 都是合法的
CreateInstance< string > 不合法,因为string没有无参的构造函数
- 转换类型约束
允许你指定另一个类型,类型实参必须可以通过一致性、引用或装箱转换隐式地转换为该类型。
class Sample < T > where T : Stream
class Example < T > where T : IComparable<T>
即类型参数必须可以通过一致性、引用或装箱转换隐式地转换为Stream类型
Sample< Stream > 合法,一致性转换
Samlple< SqlConnection >合法,引用转换
Example< int > 合法,装箱转换
你还可以规定一个类型实参必须可以转换为另一个类型实参——称为类型参数约束(type parameter constraint)
class Sample< T , U > where T : U
即类型参数T必须可以通过一致性、引用或装箱转换隐式地转换为另一个类型参数U
Sample< Stream, IDisposable > 合法,引用转换
参数类型约束的限制:指定的类不可以是结构、密封类如string或者其他特殊类型如:System.Object、System.Enum、System.ValueType、System.Delegate等。
- 组合约束
规则:
1)由于每一个值类型都有一个无参的构造函数,所以如果已经有一个值类型约束,就不允许再指定一个构造函数约束(但是T被约束成一个值类型后,仍然可以在方法内部使用无参的构造函数new T());
2)如果存在多个转换类型约束,并且其中一个为类,那么它应该出现在接口的前面,而且我们不能多次指定同一个接口。不同的类型参数可以有不同的约束,它们分别由一个单独的where引入。
class Sample< T > where T : class ,IDisposable, new()
class Sample< T > where T : struct ,IDisposable
class Sample< T, U > where T : class where U : struct ,T
class Sample < T ,U > where T : Stream where U : IDisposable
类型推断
类型推断(type inference),在使用泛型方法时,不一定要显式地声明类型实参。恰当的使用可以使代码变得更易读。C#编译器也能从你的代码中推断出越来越多的信息,同时保持语言的安全性和静态类型。
REF
深入理解C#、C#高级编程、C#游戏脚本编程、Effective C#