4.9 使用泛型創建只讀集合
問題
您希望類中的一個集合裡的信息可以被外界訪問,但不希望用戶改變這個集合。
解決方案
使用ReadOnlyCollection<T>包裝就很容易實現只讀的集合類。例子如,Lottery類包含了中獎號碼,它可以被訪問,但不允許被改變:
public class Lottery
{
// 創建一個列表.
List<int> _numbers = null;
public Lottery()
{
// 初始化內部列表
_numbers = new List<int>(5);
// 添加值
_numbers.Add(17);
_numbers.Add(21);
_numbers.Add(32);
_numbers.Add(44);
_numbers.Add(58);
}
public ReadOnlyCollection<int> Results
{
// 返回一份包裝後的結果
get { return new ReadOnlyCollection<int>(_numbers); }
}
}
Lottery有一個內部的List<int>,它包含了在構造方法中被填的中獎號碼。有趣的部分是它有一個公有屬性叫Results,通過返回的ReadOnlyCollection<int>類型可以看到其中的中獎號碼,從而使用戶通過返回的實例來使用它。
如果用戶試圖設置集合中的一個值,將引發一個編譯錯誤:
Lottery tryYourLuck = new Lottery();
// 打印結果.
for (int i = 0; i < tryYourLuck.Results.Count; i++)
{
Console.WriteLine("Lottery Number " + i + " is " + tryYourLuck.Results[i]);
}
// 改變中獎號碼!
tryYourLuck.Results[0]=29;
//最後一行引發錯誤:// Error 26 // Property or indexer
// 'System.Collections.ObjectModel.ReadOnlyCollection<int>.this[int]'
// cannot be assigned to -- it is read only
討論
ReadOnlyCollection的主要優勢是使用上的靈活性,可以在任何支持IList或IList<T>的集合中把它做為接口使用。ReadOnlyCollection還可以象這樣包裝一般數組:
int [] items = new int[3];
items[0]=0;
items[1]=1;
items[2]=2;
new ReadOnlyCollection<int>(items);
這為類的只讀屬性的標准化提供了一種方法,並使得類庫使用人員習慣於這種簡單的只讀屬性返回類型。
閱讀參考
查看MSDN文檔中的“IList”和“Generic IList”主題。
4.10 使用相應的泛型版本替換Hashtable
問題
您希望通過使用相應的泛型版本替換所有Hashtable來增強應用程序性能並使得代碼更為易讀。當您發現這些數據結構中存放結構體和值類型會導致裝箱/拆箱操作,這就變得非常有必要了。
解決方案
替換所有已存在的System.Collections.Hashtable類為速度更快的System.Collections.Generic.Dictionary泛型類。
這有一個使用System.Collections.Hashtable對象的簡單例子:
public static void UseNonGenericHashtable()
{
// 創建並填充一個Hashtable.
Hashtable numbers = new Hashtable();
numbers.Add(1, "one"); // 鍵會導致裝箱操作
numbers.Add(2, "two"); // 鍵會導致裝箱操作
// 在Hashtable顯示所有的鍵/值對.
// 在每次迭代中都會因為鍵導致一個拆箱操作
foreach (DictionaryEntry de in numbers)
{
Console.WriteLine("Key: " + de.Key + "\tValue: " + de.Value);
}
numbers.Clear();
}
下面是相同的代碼使用了System.Collections.Generic.Dictionary<T,U>對象:
public static void UseGenericDictionary()
{
// 創建並填充字典.
Dictionary<int, string> numbers = new Dictionary<int, string>();
numbers.Add(1, "one");
numbers.Add(2, "two");
// 顯示字典中的所有鍵值對.
foreach (KeyValuePair<int, string> kvp in numbers)
{
Console.WriteLine("Key: " + kvp.Key + "\tValue: " + kvp.Value);
}
numbers.Clear();
}
討論
對於應用程序中簡單的Hashtable實現,這種替換將十分容易。但有些地方需要注意,如泛型Dictionary類沒有實現ICloneable接口,而Hashtable類實現了。
表4-2顯示了兩個類中的等價成員:
表4-2 Hashtable和泛型Dictionary類的等價成員
Hashtable 類的成員 泛型 Dictionary 類的相應成員 N/A Comparer 屬性 Count屬性 Count屬性 IsFixedSize屬性 ((IDictionary)myDict).IsFixedSize IsReadOnly屬性 ((IDictionary)myDict).IsReadOnly IsSynchronized屬性 ((IDictionary)myDict).IsSynchronized Item屬性 Item屬性 Keys屬性 Keys屬性 SyncRoot屬性 ((IDictionary)myDict).SyncRoot Values屬性 Values屬性 Add 方法 Add方法 Clear方法 Clear方法 Clone方法 在重載構造方法中接收一個 IDictionary<T,U> 類型 Contains方法 ContainsKey方法 ContainsKey方法 ContainsKey方法 ContainsValue方法 ContainsValue方法 CopyTo方法 ((ICollection)myDict).CopyTo(arr,0) Remove方法 Remove方法 Synchronized static方法 lock(myDictionary.SyncRoot) {…} N/A TRyGetValue方法
表4-2中,並非所有的Hashtable和Dictionary的成員都一一對應。我們從屬性開始,注意,只有Count,Keys,Values和Item屬性在兩個類中都存在。為了彌補Dictionary中缺少的屬性,需要把它轉化為IDictionary類型。下面的代碼演示了如果進行這些轉換以獲得缺少的屬性:
Dictionary<int, string> numbers = new Dictionary<int, string>();
Console.WriteLine(((IDictionary)numbers).IsReadOnly);
Console.WriteLine(((IDictionary)numbers).IsFixedSize);
Console.WriteLine(((IDictionary)numbers).IsSynchronized);
Console.WriteLine(((IDictionary)numbers).SyncRoot);
注意,由於缺少返回一個泛型字典同步版本的代碼,IsSynchronized屬性將總是返回false。SyncRoot屬性在被調用時總是返回相同的對象。實際上這個屬性返回的是this指針。微軟已經決定移除泛型集合類的創建同步包裝的功能。
做為替代,他們推薦使用lock關鍵字鎖住整個集合或其他同步對象類型以滿足您的需求。
因為在泛型字典類中也缺少了克隆方法(實際是是因為這個類沒有實現ICloneable接口),您可以轉而使用重載的構造方法來接收一個IDictionary<T,U>類型:
// 創建並填充字典.
Dictionary<int, string> numbers = new Dictionary<int, string>();
numbers.Add(1, "one");
numbers.Add(2, "two");
// 顯示原字典的鍵/值對.
foreach (KeyValuePair<int, string> kvp in numbers)
{
Console.WriteLine("Original Key: " + kvp.Key + "\tValue: " + kvp.Value);
}
// 克隆字典對象.
Dictionary<int, string> clonedNumbers = new Dictionary<int, string>(numbers);
// 顯示克隆字典中的鍵/值對.
foreach (KeyValuePair<int, string> kvp in numbers)
{
Console.WriteLine("Cloned Key: " + kvp.Key + "\tValue: " + kvp.Value);
}
還有兩個Dictionary類中缺少的方法:Contains和CopyTo方法。Contains方法的功能在Dictionary類中很容易被實現。在Hashtable類中,Cintains方法和ContainsKey方法有相同的行為,因此您可以在Dictionary類中簡單地使用ContainsKey方法來模擬Hashtable類中的Contains方法:
// 創建和填充字典.
Dictionary<int, string> numbers = new Dictionary<int, string>();
numbers.Add(1, "one");
numbers.Add(2, "two");
Console.WriteLine("numbers.ContainsKey(1) == " + numbers.ContainsKey(1));
Console.WriteLine("numbers.ContainsKey(3) == " + numbers.ContainsKey(3));
CopyTo方法也很容易在Dictionary類中被模擬,但需要做一些額外的工作:
// 創建和填充字典.
Dictionary<int, string> numbers = new Dictionary<int, string>();
numbers.Add(1, "one");
numbers.Add(2, "two");
// 顯示字典中的所有鍵/值對.
foreach (KeyValuePair<int, string> kvp in numbers)
{
Console.WriteLine("Key: " + kvp.Key + "\tValue: " + kvp.Value);
}
// 創建對象數組來拷貝字典對象中的信息.
KeyValuePair<int, string>[] objs = new KeyValuePair<int,
string>[numbers.Count];
// 調用字典中的CopyTo方法
// 把字典中的所有鍵/值對對象拷貝到objs中
((IDictionary)numbers).CopyTo(objs, 0);
// 顯示objs[]中的所有鍵/值對.
foreach (KeyValuePair<int, string> kvp in objs)
{
Console.WriteLine("Key: " + kvp.Key + "\tValue: " + kvp.Value);
}
調用Dictionary對象中的CopyTo方法需要創建一個KeyValuePair<T,U>對象數組,它用於在CopyTo方法被調用之後,控制字典對象中的所有KeyValuePair<T,U>對象。接下來numbers字典對象被轉換為IDictionary類型以調用CopyTo方法。一旦CopyTo方法被調用,objs數組將包含原numbers對象中的所有KeyValuePair<T,U>對象。注意objs數組迭代時使用了foreach循環,這點和numbers對象是相同的。