《CLR via C#》读书笔记(7) -- 泛型(上)
1. 初识泛型
泛型是指在定义一个类型的时候,不用将该类型所操作的对象的类型确定,而是交给调用者来确定。该类型的实现只是实现一些通用的行为。
例如:
public class Util<T> { public void PrintContent(T content) { Console.WriteLine(content.ToString()); } }
代码定义了一个泛型类,其中"T"是该泛型类的类型参数.用它来代表将来可能要操作的对象的类型。
而在该类型的实现中,我们可以赋予一些通用的行为。比如此处的PrintContent.
这种定义了类型参数的类型在C#中称为开放类型。开放类型不能被实例化。
在使用时:
Util<int> util = new Util<int>(); util.PrintContent(2);
此处实例化的时候指定了类型参数的实参。这种指定了类型实参的类型称为封闭类型。
在泛型的类型中,我们还可以定义泛型方法:
public class Util<T> { public void PrintContent(T content) { Console.WriteLine(content.ToString()); } // 此处TM是为Print方法定义的类型参数,它可以不同于类型级别的类型参数T.但是它的有效范围为该方法。
// 注意:此处的类型参数的名字是随便定的。但是微软建议采用T开头,就像接口中I开头一样。
public void Print<TM>(TM content) { Console.WriteLine(content.ToString()); } }
和使用泛型一样,我们应该像这样使用泛型方法:
Util<int> util = new Util<int>(); util.Print<string>("abc");//调用泛型方法时指定类型实参。
2. 泛型的好处
如果没有泛型的话,我们想定义一个面向通用数据类型的类型或方法时。必须将接收的数据参数类型定义为Object。如下:
public class Util { public void PrintContent(object content) { Console.WriteLine(content.ToString()); } public object GetObj()
{
//implementation.
}
}
这里如果要操作的是一个值类型,比如说int,则要如下使用:
Util util = new Util(); util.PrintContent(2); //上一行代码中整数2需要被装箱,这会影响性能。
int v = (int)util.GetObj();
//上一行代码中需要将结果拆箱,这也会影响性能。
//其实即使是引用类型,不需要装箱拆箱。参数和返回值如果定为object,调用方在得到结果后也需要繁琐的转换为他想要的类型
//这样做首先是不方便,然后这也是一种不太安全的做法。有可能会转型失败
//因此泛型其实也是C#中类型安全的体现。
如果使用了泛型,这些类型转换将不再需要。因此能提升性能。
3.类型推断
C#编译器为了方便像我们这种码农,提供了很多智能的功能。这使我们的代码看起能更简捷易懂。类型推断功能便是其中的一个。
当我们使用泛型方法时,我们可以不用显示指定类型实参,C#编译器可以通过我们传递参数来智能的分析出我们的类型实参应该是什么。例如:
public class Util { public void Print<T>(T content) { Console.WriteLine(content.ToString()); } } Util util = new Util(); util.Print(2);//此处我们不用指定类型实参,编译器会从我们传递的参数2中推断出此处的类型实参应该是Int32.
这样子的话,我们便可以少写很多的"<"和">".
4.类型约束
CLR提供了对泛型的类型实参进行约束的功能。方便创建面向特定类型的泛型类型。
经常用的约束主要有:主要约束,次要约束,构造器约束. 在定义泛型类的时候,这些约束可以通过where关键字来定义
主要约束
主要约束是用来将类型实参约束为引用类型/值类型或者某个类型的派生类。例如:
//将类型实参约束为引用类型,使用该泛型时必须将类型实参指定为引用类型。 public class Util<T> where T:class { public void Print<T>(T content) { Console.WriteLine(content.ToString()); } }
//将类型实参约束为值类型,使用该泛型时必须将类型实参指定为值类型。 public class Util<T> where T:struct { public void Print<T>(T content) { Console.WriteLine(content.ToString()); } }
//将类型实参约束为Person类的派生类,使用该泛型类时。类型实参必须为Person类,或其派生类。 public class Util<T> where T:Person { public void Print<T>(T content) { Console.WriteLine(content.ToString()); } }
次要约束
次要约束主要是将类型实参约束为对某个接口的实现以及对多元类型实参中各元之间的关系的约束。例如:
// 约束类型参数必须实现了IDisposable接口。
public class MessageHandler<T> where T:IDisposable { }
// 约束类型参数必须实现了IDisposable,ICloneable接口,并且T2必须继承于T1 public class MessageHandler<T1,T2> where T1:IDisposable,ICloneable where T2:T1 { }
构造器约束
构造器约束是将类型实参约束为可以通过无参构造函数进行实例化的类型。例如:
// 约束类型参数必须通够通过无参构造函数实例化 public class MessageHandler<T> where T:new() { }
5. 为泛型变量提供默认值
在泛型的实现中,我们有可能定义泛型类型的变量。例如:
public class MessageHandler<T> where T:new() { public T GetMessage() {
T message;//此处会会编译错误,因为我们没有初始化message变量。但是在不知道具体类型的情况下如何初始化呢? return message; } }
上面的代码引出了如何初始化泛型变量的问题。CLR为我们解决了这个问题。在C#中我们可以用default关键字来表示给一个泛型变量赋予一个初始值(将值类型的内存块清零,或者将引用类型变量赋为null)。例如:
public class MessageHandler<T> where T:new() { public T GetMessage() { return default(T); } }