1. 泛型概述
有了泛型,就可以创建独立于被包含类型的类和方法了,我们不必给不同的类型编写功能相同的许多方法或类,值创建一个方法或类即可。另一个减少代码的选项是使用Object类,但是其不是类型安全的。泛型类使用泛型类型,并可以根据需要用特定的类型替换泛型类型。这就保证了类型安全性:如果某个类型不支持泛型类,编译器就会出现错误。 下面介绍泛型的优点和缺点,尤其是:- 性能
- 类型安全性
- 二进制代码重用
- 代码的扩展
- 命名约定
1.1 性能
值类型存储在栈上,引用类型存储在堆上。C#类是引用类型,结构是值类型。.NET很容易把值类型转换为引用类型,所以可以在需要对象(对象是引用类型)的任意地方使用值类型。**从值类型转换为引用类型成为装箱**。如果方法需要把一个对象作为参数,同时传递一个值类型,此时装箱操作会自动进行。另一方面,装箱的值类型可以使用拆箱操作转换为值类型。在拆箱时,需要使用类型强制转换运算符。System.Collections: ArrayList类
var list = new ArrayList();
list.Add(44); //装箱操作
int i1 = (int)list[0]; //拆箱操作
foreach (int i2 in list)
{
Console.WriteLine(i2); //拆箱操作
}
装箱和拆箱操作很容易使用,但性能损失比较大,遍历许多项时尤其如此。
System.Collections.Generic: List<T>
List<类>不使用对象,而是在使用时定义类型,在下面的例子中,List<T>类的泛型类型定义为int,所以int类型在JIT编译器动态生成的类中使用,不再进行装箱和拆箱操作
var list = new List<int>();
list.Add(44);
int i1 = list[0];
foreach (int i2 in list)
{
Console.WriteLine(i2);
}
1.2 类型安全
泛型的另一个特性是类型安全。与ArrayList类一样,如果使用对象,就可以在这个集合中添加任意类型。例如我们在ArrayList的几何中添加一个整数、一个字符串和一个MyClass类。如果使用foreach迭代,编译器会编译这段代码,但是会出现一个运行异常,因为不是所有的元素都可以强制转换为int类型。 错误应尽早发现。在泛型类List中,泛型类型T定义了允许使用的类型。如果添加了其他类型,则编译器不会编译这段代码。1.3 二进制代码的重用
泛型允许更好的重用二进制代码。泛型类可以定义一次,并且可以用许多不同的类型实例化。不需要像C++那样访问源代码。var list = new List<int>();
list.Add(44);
var stringList = new List<string>();
stringList.Add("mystring");
var myClassList = new List<myClass>();
myClassList.Add(new MyClass());
泛型类型可以在一种语言中定义,在任何其他.NET语言中使用。
1.4 代码的扩展
1. 因为泛型类的定义会放在程序集中,所以用特定类型实例化泛型类不会在IL代码中复制这些类 2. JIT编译器把泛型类编译为本地代码时,会给每个值创建一个新类。因为值类型包含在实例化的泛型类的内存中,同时因为每个值类型对内存的要求都不同,所以要为每个值类型实例化一个新类 3 引用类型共享同一个本地类的所有相同的实现代码。这是因为引用类型在实例化的泛型类中只需要4个字节的内存地址(32位系统)1.5 命名约定
下面是泛型类的命名规则:- 泛型类型的名称用字母T作为前缀
- 如果没有特殊的要求,泛型类型允许用任意类替代,且只使用了一个泛型类型,据可以用字符T作为泛型类型的名称
- 如果泛型类型有特定的要求(例如,它必须实现一个接口或派生自基类),或者使用了两个或多个泛型类型,就应该给泛型类型使用描述性的名称
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
public delegate TOutput Converter<TInput, TOutput>(TInput from);
public class SortedList<TKey, TValue>();
2. 创建泛型类
首先介绍一个一般的、非泛型的简化链表类,它可以包含任意类型的对象,之后再把这个类型转换为泛型类。 public class LinkNodeList
{
/*
* 链表中,一个元素引用下一个元素
* Value:初始化构造函数
* Previous:上一个元素的引用
* Next:下一个元素的引用
*/
public LinkNodeList(object value)
{
this.Value = value;
}
public object Value { get; private set; }
public LinkNodeList Previous { get; internal set; }
public LinkNodeList Next { get; internal set; }
}
public class LinkedList
{
/*
* LinkeList类包含LinkedNodeList类型的两个属性Last,First;
* First: 链表的头
* Last: 链表的尾
* AddLast(): 在链表尾添加一个新元素
* GetEnumerator(): 通过其实现foreach遍历
*/
public LinkNodeList First { get; private set; }
public LinkNodeList Last { get; private set; }
public LinkNodeList AddLast(object node)
{
var newNode = new LinkNodeList(node);
if (First == null)
{
First = newNode;
Last = First;
}
else
{
LinkNodeList previous = Last;
Last.Next = newNode;
Last = newNode;
Last.Previous = previous;
}
return newNode;
}
public IEnumerator GetEnumerator()
{
LinkNodeList current = First;
while (current != null)
{
yield return current.Value;
current = current.Next;
}
}
}
使用LinkedList的示例:
class Program
{
static void Main(string[] args)
{
LinkedList testList = new LinkedList();
testList.AddLast(1);
testList.AddLast(2);
testList.AddLast(3);
foreach (int i in testList)
{
Console.WriteLine(i);
}
Console.ReadLine();
}
}
下面创建链表的泛型版本。泛型类的定义与一般类类似,只是要使用泛型类型声明。之后,泛型类型就可以在类中用作一个字段成员,或者方法的参数类型。
public class LinkNodeList<T>
{
public LinkNodeList(T value)
{
this.Value = value;
}
private T Value {get; private set;}
private LinkNodeList<T> Next {get; internal set;}
private LinkNodeList<T> Prev {get; internal set;}
}
下面的代码将LinkedList类也改为泛型类:
public class LinkedList<T>:IEnumerator<T>
{
public LinkNodeList<T> First {get; private set;}
public LinkNodeList<T> Last {get; private set;}
public LinkNodeList<T> AddLast(T node)
{
var newNode = new LinkNodeList<T>(node);
if (First == null)
{
First = newNode;
Last = First;
}
else
{
LinkNodeList<T> previous = Last;
Last.Next = newNode;
Last = newNode;
Last.Prev = previous;
}
public IEnumerator<T> GetEnumerator()
{
LinkNedeList<T> current = First;
while (current != null)
{
yield return current.Value;
current = current.Next;
}
}
}
}
3. 泛型类的功能
本节讨论下面四个主题:默认值、约束、继承、静态成员//泛型文档管理器的示例
//先创建一个新的控制台项目DocumentManager, 并添加DocumentManager<I>类。AddDocument()方法将一个文档添加到队列中。如果队列不为孔,IsDocumentAvailable只读属性就返回true
using System;
using System.Collections.Generics
{
namespace Wrox.ProCSharp.Generic
{
public class DocumentManager<T>
{
private readonly Queue<T> documentQuene = new Queue<T>;
public void AddDocument()
{
lock(this)
{
documentQueue.Enqueue(doc);
}
}
public bool IsDocumentAvailable
{
get { return documentQueue.Count > 0; }
}
}
}
}
3.1 默认值
当需要将类型T指定为null时,不能把null赋予泛型类型。原因是泛型类型也可以实例化为值类型,而null只能应用于引用类型。
在CSharp中,可以使用default关键字,将null赋予引用,将0赋予值类型
public T GetDocument()
{
T doc = default(T);
lock(this)
{
doc = documentQueue.Dequeue();
}
return doc;
}