按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
{
private LinkedListNode first;
public LinkedListNode First
{
get { return first; }
}
private LinkedListNode last;
public LinkedListNode Last
{
get { return last; }
}
public LinkedListNode AddLast(T node)
{
LinkedListNode newNode = new LinkedListNode(node);
if (first null)
{
first = newNode;
last = first;
}
else
{
last。Next = newNode;
last = newNode;
}
return newNode;
}
public IEnumerator GetEnumerator()
{
LinkedListNode current = first;
while (current != null)
{
yield return current。Value;
current = current。Next;
}
}
IEnumerator IEnumerable。GetEnumerator()
{
return GetEnumerator();
}
}
使用泛型类LinkedList,可以用int类型实例化它,且无需装箱操作。如果不使用AddLast()方法传送int,就会出现一个编译错误。使用泛型IEnumerable,foreach语句也是类型安全的,如果foreach语句中的变量不是int,也会出现一个编译错误。
LinkedList list2 = new LinkedList();
list2。AddLast(1);
list2。AddLast(3);
list2。AddLast(5);
foreach (int i in list2)
{
Console。WriteLine(i);
}
同样,可以给泛型LinkedList使用string类型,将字符串传送给AddLast()方法。
LinkedList list3 = new LinkedList();
list3。AddLast(〃2〃);
list3。AddLast(〃four〃);
list3。AddLast(〃foo〃);
foreach (string s in list3)
{
Console。WriteLine(s);
}
提示:
每个处理对象类型的类都可以有泛型实现方式。另外,如果类使用了继承,泛型非常有助于去除类型转换操作。
9。3 泛型类的特性
在创建泛型类时,需要一些其他C#关键字。例如,不能把null赋予泛型类型。此时,可以使用default关键字。如果泛型类型不需要Object类的功能,但需要调用泛型类上的某些特定方法,就可以定义约束。
本节讨论如下主题:
● 默认值
● 约束
● 继承
● 静态成员
下面开始一个使用泛型文档管理器的示例。文档管理器用于从队列中读写文档。先创建一个新的控制台项目DocumentManager,添加类DocumentManager。AddDocument()方法将一个文档添加到队列中。如果队列不为空,IsDocumentAvailable只读属性就返回true。
using System;
using Systemllections。Generic;
namespace Wrox。ProCSharp。Generics
{
public class DocumentManager
{
private readonly Queue documentQueue = new Queue();
public void AddDocument(T doc)
{
lock (this)
{
documentQueue。Enqueue(doc);
}
}
public bool IsDocumentAvailable
{
get { return documentQueueunt 》 0; }
}
}
}
9。3。1 默认值
现在给DocumentManager类添加一个GetDocument()方法。在这个方法中,给类型T指定null。但是,不能把null赋予泛型类型。原因是泛型类型也可以实例化为值类型,而null只能用于引用类型。为了解决这个问题,可以使用default关键字。通过default关键字,将null赋予引用类型,将0赋予值类型。
public T GetDocument()
{
T doc = default(T);
lock (this)
{
doc = documentQueue。Dequeue();
}
return doc;
}
注意:
default关键字根据上下文可以有多种含义。switch语句使用default定义默认情况。在泛型中,根据泛型类型是引用类型还是值类型,default关键字用于将泛型类型初始化为null或0。
9。3。2 约束
如果泛型类需要调用泛型类型上的方法,就必须添加约束。对于DocumentManager,文档的标题应在DisplayAllDocuments()方法中显示。
Document类执行带有Title和Content属性的IDocument接口:
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;
thisntent = content;
}
public string Title { get; set; }
public string Content { get; set; }
}
要使用DocumentManager类显示文档,可以将类型T强制转换为IDocument接口,以显示标题:
public void DisplayAllDocuments()
{
foreach (T doc in documentQueue)
{
Console。WriteLine((IDocument)doc)。Title);
}
}
问题是,如果类型T没有执行IDocument接口,这个类型转换就会生成一个运行异常。最好给DocumentManager类定义一个约束:TDocument类型必须执行IDocument接口。为了在泛型类型的名称中指定该要求,将T改为TDocument。where子句指定了执行IDocument接口的要求。
public class DocumentManager
where TDocument : IDocument
{
这样,就可以编写foreach语句,让类型T包含属性Title了。Visual Studio IntelliSense和编译器都会提供这个支持。
public void DisplayAllDocuments()
{
foreach (TDocument doc in documentQueue)
{
Console。WriteLine(doc。Title);
}
}
在Main()方法中,DocumentManager类用Document类型实例化,而Document类型执行了需要的IDocument接口。接着添加和显示新文档,检索其中一个文档:
static void Main()
{
DocumentManager dm = new DocumentManager();
dm。AddDocument(new Document(〃Title A〃; 〃Sample A〃));
dm。AddDocument(new Document(〃Title B〃; 〃Sample B〃));
dm。DisplayAllDocuments();
if (dm。IsDocumentAvailable)
{
Document d = dm。GetDocument();
Console。WriteLine(dntent);
}
}
DocumentManager现在可以处理任何执行了IDocument接口的类。
在示例应用程序中,介绍了接口约束。泛型还有几种约束类型,如表9…1所示。
表 9…1
约 束
说 明
where T : struct
使用结构约束,类型T必须是值类型
where T : class
类约束指定,类型T必须是引用类型
where T : IFoo
指定类型T必须执行接口IFoo
where T : Foo
指定类型T必须派生于基类Foo
where T : new()
这是一个构造函数约束,指定类型T必须有一个默认构造函数
where T : U
这个约束也可以指定,类型T1派生于泛型类型T2。该约束也称为裸类型约束
注意:
在CLR 2。0中,只能为默认构造函数定义约束,不能为其他构造函数定义约束。
使用泛型类型还可以合并多个约束。where T : IFoo,new()约束和MyClass声明指定,类型T必须执行IFoo接口,且必须有一个默认构造函数。
public class MyClass
where T : IFoo; new()
{
//。。。
提示:
在C#中,where子句的一个重要限制是,不能定义必须由泛型类型执行的运算符。运算符不能在接口中定义。在where子句中,只能定义基类、接口和默认构造函数。
9。3。3 继承
前面创建的LinkedList类执行了IEnumerable接口:
public class LinkedList : IEnumerable
{
//。。。
泛型类型可以执行泛型接口,也可以派生于一个类。泛型类可以派生于泛型基类:
public class Base
{
}
public class Derived : Base
{
}
其要求是必须重复接口的泛型类型,或者必须指定基类的类型,如下所示:
public class Base
{
}
public class Derived : Base
{
}
于是,派生类可以是泛型类或非泛型类。例如,可以定义一个抽象的泛型基类,它在派生类中用一个具体的类型实现。这允许对特定类型执行特殊的操作:
public abstract class Calc
{
public abstract T Add(T x; T y);
public abstract T Sub(T x; T y);
}
public class SimpleCalc : Calc
{
public override int Add(int x; int y)
{
return x + y;
}
public override int Sub(int x; int y)
{
return x … y;
}
}
9。3。4 静态成员
泛型类的静态成员需要特别关注。泛型类的静态成员只能在类的一个实例中共享。下面看一个例子。StaticDemo类包含静态字段x:
public class StaticDemo
{
public static int x;
}
由于对一个string类型和一个int类型使用了StaticDemo类,所以存在两组静态字段:
StaticDemo。x = 4;
StaticDemo。x = 5;
Console。WriteLine(StaticDemo。x); // writes 4
9。4 泛型接口
使用泛型可以定义接口,接口中的方法可以带泛型参数。在链表示例中,就执行了IEnumerable接口,它定义了GetEnumerator()方法,以返回IEnumerator。对于 1。0中的许多非泛型接口, 从2。0开始定义了新的泛型版本,例如Iparable:
public interface Iparable
{
int pareTo(T other);
}
第5章中的非泛型接口Iparable需要一个对象,Person类的pareTo()方法才能按姓氏给人员排序:
public class Person : Iparable
{
public int pareTo(object obj)
{
Person other = obj as Person;
return this。lastnamepareTo(other。lastname);
}
//。。。
执行泛型版本时,不再需要将object的类型强制转换为Person:
public class Person : Iparable
{
public int pareTo(Person other)
{
return this。lastnamepareTo(other。lastname);
}
//。。。
9。5 泛型方法
除了定义泛型类之外,还可以定义泛型方法。在泛型方法中,泛型类型用方法声明来定义。
Swap方法把T定义为泛型类型,用于两个参数和一个变量temp:
void Swap(ref T x; ref T y)
{
T temp;
temp = x;
x = y;
y = temp;
}
把泛型类型赋予方法调用,就可以调用泛型方法:
int i = 4;
int j = 5;
Swap(ref i; ref j);
但是,因为C#编译器会通过调用Swap方法来获取参数的类型,所以不需要把泛型类型赋予方法调用。泛型方法可以像非泛型方法那样调用:
int i = 4;
int j = 5;
Swap(ref i; ref j);
下面的例子使用泛型方法累加集合中的所有元素。为了说明泛型方法的功能,下面的Account类包含name和balance:
public class Account
{
private string name;
public string Name
{
get
{
return name;
}
}
private decimal balance;
public decimal Balance
{
get
{
return balance;
}
}
public Account(string name; Decimal balance)
{
this。name = name;
this。balance = balance;
}
}
应累加结余的所有账目操作都添加到List类型的账目列表中:
List accounts = new List();
accounts。Add(new Account(〃Christian〃; 1500));
accounts。Add(n