第六章 泛型
1.优缺点
1.性能:避免装箱拆箱操作
例如:
public void List()
{
var list = new ArrayList();
list.Add(44);//装箱操作 Int转成引用类型
int i = (int)list[0];//拆箱 引用类型转为Int
}
public void List2()
{
var list = new List<int>();
list.Add(0);//没有装箱
int i = list[0];//没有拆箱
}
2.类型安全
泛型的另一个有点就是类型安全。比如上例中ArrayList中可以添加任意类型,但List<int>可以确保只能添加int型值。
3.二进制代码重用
4.代码的扩展
2.创建泛型类
下面例子是一个非泛型的简化链表,他可以包含任意类型的对象。
/// <summary>
/// 节点类
/// </summary>
public class LinkedListNode
{
public object Value { get; private set; }
public LinkedListNode(object value)
{
this.Value = value;
}
public LinkedListNode Next { get; internal set; }
public LinkedListNode Prev { get; internal set; }
}
/// <summary>
/// 链表
/// </summary>
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;
}
}
}
现在对链表进行操作:
var list1 = new LinkedList();
list1.AddLast(2);
list1.AddLast(4);
list1.AddLast("6");
foreach (int item in list1)
{
Console.WriteLine(item);
}
上面操作实现了在链表中添加两个整数类型和一个字符串类型。数据类型要转换成一个对象,所以要进行装箱操作。通过foreach语句执行拆箱操作。在foreach语句中,将链表中的元素强制转换为整数,对于链表的第三个元素会发生一个运行异常。
创建泛型类:泛型类的定义与一般类类似,只要使用泛型类声明。之后泛型类型就可以在类中用作一个字段成员、或者方法的参数类型。
/// <summary>
/// 节点类
/// </summary>
public class LinkedListNode<T>
{
public T Value { get; private set; }
public LinkedListNode(T value)
{
this.Value = value;
}
public LinkedListNode<T> Next { get; internal set; }
public LinkedListNode<T> Prev { get; internal set; }
}
/// <summary>
/// 链表
/// </summary>
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 GetEnumerator()
{
return GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
LinkedListNode<T> current = First;
while (current != null)
{
yield return current.Value;
current = current.Next;
}
}
}
使用泛型类,可以用int类型实例化他,无需装箱操作。但如果不适用AddLast()方法传递一个int,就会报错。
var list = new LinkedList<int>();
list.AddLast(1);
list.AddLast(3);
list.AddLast(4);
3.泛型类的功能
1.默认值
介绍一个泛型文档管理器的示例。文档管理器用于从队列中读写文档。
public class DocumentManager<T>
{
private readonly Queue<T> documentQuene = new Queue<T>();
/// <summary>
/// 将文档添加到队列中
/// </summary>
/// <param name="doc"></param>
public void AddDocument(T doc)
{
lock (this)
{
documentQuene.Enqueue(doc);
}
}
/// <summary>
/// 队列是否为空
/// </summary>
public bool IsDocumentAvailable
{
get { return documentQuene.Count > 0; }
}
/// <summary>
/// 获取文档的方法
/// </summary>
/// <typeparam name="T"></typeparam>
public T GetDocument()
{
T doc = default(T);
lock (this)
{
doc = documentQuene.Dequeue();
}
return doc;
}
}
在获取文档的方法中,应该吧类型T指定为null,T doc=null。但是,不能把null赋予泛型类型。原因是泛型类型也可以实例化值类型,而null只能用于引用类型。为了解决这个问题,可以使用default关键字。用过default关键字,将null赋值给引用类型,将0赋值给值类型。
2.约束
如果泛型类需要调用泛型类型T中的方法,就必须添加约束。
public interface IDocument
{
string Title { get; set; }
string Content { get; set; }
}
public class Document : IDocument
{
public Document()
{
}
public Document(string title,string content)
{
this.Title = title;
this.Content = content;
}
public string Content { get; set; }
public string Title { get; set; }
}
定义了接口IDocument和一个泛型类型Document,Document实现接口IDocument.
DocumentManager<T>泛型类中添加一个实现显示所有文档Title的方法DisplayAllDocument()。要使用DocumentManager<T>类显示文档,可以将类型T强制转换为IDocument接口,以显示标题。
public void DisplayAllDocument()
{
foreach (T doc in documentQuene)
{
Console.WriteLine(((IDocument)doc).Title);
}
}
问题是,如果T类型没有实现IDocument接口,这个强制转换就运行一个异常。最好给DocumentManager<T>定义一个约束,T必须实现接口IDocument.where自居指定实现IDocument接口的要求。
public class DocumentManager<T>
where T:IDocument
{
}
这样就可以编写foreach语句了。
public void DisplayAllDocument()
{
foreach (T doc in documentQuene)
{
Console.WriteLine(doc.Title);
}
}
在main()方法中,用Document泛型类型初始化DocumentManager<T>类。
var dm = new DocumentManager<Document>();
dm.AddDocument(new Document("Title 1","Content 1"));
dm.AddDocument(new Document("Title 2", "Content 2"));
dm.DisplayAllDocument();
泛型接受几种约束类型:
约 束 |
说 明 |
Where T:struct |
对于结构约束,类型T必须是值类型 |
Where T:class |
类约束指定类型T必须是引用类型 |
Where T:IFoo |
指定类型T必须实现接口IFoo |
Where T:Foo |
指定类型T必须派生自基类Foo |
Where T:new() |
这是一个构造函数约束,指定类型T必须有一个默认构造函数 |
Where T1:T2 |
这个约束也可以指定,类型T1派生自泛型类型T2. |
3.继承
前面创建的LinkedList<T>类实现了IEnumerable<T>接口:
public class LinkedList<T> : IEnumerable<T> { }
泛型类型可以实现泛型接口,也可以派生自一个类。泛型类可以派生自泛型基类:
public class Base<T> {}
public class Deriver<T> :Base<T>
其要求是必须重复接口的泛型类型,或者必须指定基类的类型,如下:
Public class Base<T> { }
Public class Derived<T>: Base<String> { }
4.静态成员
泛型类的静态成员需要特变关注。泛型类的静态成员只能在一个实例中共享!(不同类型的泛型类型之间不共享静态变量)
例如:
public class StaticDemo<T>
{
public static int x;
}
StaticDemo<string>.x = 4;
StaticDemo<int>.x = 5;
Console.WriteLine(StaticDemo<int>.x);
输出结果为5
StaticDemo<string>.x = 4;
StaticDemo<int>.x = 5;
Console.WriteLine(StaticDemo< string >.x);
输出结果为4
5.泛型接口