9.5 約束
有的時候,您必須確保添加進泛型列表中的元素擁有某些約束(例如,它們從一個給定的基類繼承或它們實現了指定的接口)。在下面的例子中,我們實現了一個簡單的可排序的單鏈表。鏈表由多個Node組成,每個Node必須保證添加進去的元素實現了IComparer接口。您可以這樣聲明:
public class Node<T> : IComparable<Node<T>> where T : IComparable<T>
這句代碼定義了一個泛型Node,它操作類型T。Node中的T實現了IComparable<T>接口,這意味著兩個Node的T可以進行對比。Node類通過約束(where T : IComparable<T>)來操作那些實現了IComparable接口的類型。因此您可以使用任何類型來替代T,只要那種類型實現了IComparable接口
例9-12舉例說明了完整的接口實現,並進行分析如下。
例9-12 使用約束
using System;
using System.Collections.Generic;
namespace UsingConstraints
{
public class Employee : IComparable<Employee>
{
private string name;
public Employee(string name)
{
this.name = name;
}
public override string ToString()
{
return this.name;
}
//實現接口
public int CompareTo(Employee rhs)
{
return this.name.CompareTo(rhs.name);
}
public bool Equals(Employee rhs)
{
return this.name == rhs.name;
}
}
//節點必須實現Node<T>的IComparable接口。
//通過where關鍵字約束Node只接收實現了IComparable接口的項
public class Node<T> : IComparable<Node<T>> where T : IComparable<T>
{
//成員變量
private T data;
private Node<T> next = null; //下一個節點
private Node<T> prev = null; //前一個節點
//構造方法
public Node(T data)
{
this.data = data;
}
//屬性
public T Data
{
get { return this.data; }
}
public Node<T> Next
{
get { return this.next; }
}
public int CompareTo(Node<T> rhs)
{ //這樣使用是因為約束
return data.CompareTo(rhs.data);
}
public bool Equals(Node<T> rhs)
{
return this.data.Equals(rhs.data);
}
//方法
public Node<T> Add(Node<T> newNode)
{ //下面的“我”代表類的當前實例
if (this.CompareTo(newNode) > 0) //小於我則放在我前面
{
newNode.next = this; //新節點的下一節點指向我
//如果我有前一個節點,則新節點的前一個節點指向它
//新節點做為它的下一個節點
if (this.prev != null)
{
this.prev.next = newNode;
newNode.prev = this.prev;
}
//設置我的前一個節點為新節點
this.prev = newNode;
//從下面的LinkedList<T>代碼可以得知,添加都是從
//頭節點開始判斷,只有新節點為頭節點時才返回它
return newNode;
}
else //大於等於我則放在我後面
{
//如果我有下一個,則跟下一個進行對比
//這裡使用了遞歸,直到新節點找到比它大的節點為止
if (this.next != null)
{
this.next.Add(newNode);
}
//如果我沒有下一個節點,則設置新節點為我的下一個
//節點,並把它的上一個節點指向我
else
{
this.next = newNode;
newNode.prev = this;
}
return this;
}
}
public override string ToString()
{
string output = data.ToString();
if (next != null)
{ //這裡也使用了遞歸打印鏈表上的所有元素
output += ", " + next.ToString();
}
return output;
}
}
public class LinkedList<T> where T : IComparable<T>
{
//成員變量
private Node<T> headNode = null;
//索引器
public T this[int index]
{
get
{ //由於是鏈表,這裡需要從頭遍歷
int ctr = 0;
Node<T> node = headNode;
while (node != null && ctr <= index)
{
if (ctr == index)
{
return node.Data;
}
else
{
node = node.Next;
}
++ctr;
}
throw new ArgumentOutOfRangeException();
}
}
//默認構造方法
public LinkedList()
{
}
//方法
public void Add(T data)
{
if (headNode == null)
{
headNode = new Node<T>(data);
}
else
{
headNode = headNode.Add(new Node<T>(data));
}
}
public override string ToString()
{
if (this.headNode != null)
{
return this.headNode.ToString();
}
else
{
return string.Empty;
}
}
}
//測試類
class Test
{
static void Main()
{ //創建一個實例來進行方法
Test t = new Test();
t.Run();
}
public void Run()
{
LinkedList<int> myLinkedList = new LinkedList<int>();
Random rand = new Random();
Console.Write("Adding: ");
for (int i = 0; i < 10; i++)
{
int nextInt = rand.Next(10);
Console.Write("{0} ", nextInt);
myLinkedList.Add(nextInt);
}
LinkedList<Employee> employees = new LinkedList<Employee>();
employees.Add(new Employee("John"));
employees.Add(new Employee("Paul"));
employees.Add(new Employee("George"));
employees.Add(new Employee("Ringo"));
Console.WriteLine("\nRetrieving collections");
Console.WriteLine("Integers: " + myLinkedList);
Console.WriteLine("Employees: " + employees);
}
}
}
運行結果:
Adding:2 1 2 6 1 5 9 0 5
Retrieving collections…
Integers:0,1,1,1,2,2,5,5,6,9
Employees:George, John, Paul, Ringo
本例的開頭聲明了一個可以放到鏈表中的類:
public class Employee : IComparable<Employee>
這個聲明指出Employee對象是可以進行對比的,可以看到Employee類實現了CompareTo和Equals方法。注意:這些方法是類型安全的(從參數傳遞進去的類是Employee類型)。LinkedList本身聲明為只操作實現了IComparable接口的類型:
public class LinkedList<T> where T : IComparable<T>
這樣就可以保證對列表進行排序。LinkedList操作Node類型的對象。Node也實現了IComparable接口,並要求它本身所操作的數據也實現了IComparable接口:
public class Node<T> : IComparable<Node<T>> where T : IComparable<T>
這些約束使得Node實現CompareTo方法變得安全而簡單,因為Node知道它將和其它Node的數據進行對比:
public int CompareTo(Node<T> rhs)
{
// 這樣使用是因為約束
return data.CompareTo(rhs.data);
}
注意,我們不需要測試rhs從而得知它是否實現了IComparable接口;我們已經約束了Node只能操作實現了IComparable接口的數據。