當我們在系統用到某些佔用記憶體較多的大物件,且該物件並不會被頻繁使用(例如快取場景)時,若考慮效能因素,或許我們可以選擇使用弱參考(WeakReference)物件。弱參考物件就像是物件之中的「無間行者」,行走於「活動」與「非活動」狀態之間。即使該物件存在參考,垃圾回收器仍然可以對其進行回收,這使得我們對該物件的呼叫始終存在一種不可預知性,除非我們透過 Target 屬性賦給物件,以建立強參考,否則我們始終處於這種憂慮之中。這讓我們常常感到左右為難,但在一些追求效能的場景下,使用弱參考未嘗不是明智的選擇。只要我們遵循一定的原則,例如在每次呼叫弱參考物件時,首先判斷其是否為 null,就不會存在太大的問題(如果考慮並發,則需要 lock,通常需要做兩次對 null 的判斷,就如在 Singleton 模式中對並發支援的實現一樣)。然而,當我們在一個集合物件中儲存弱參考物件時,問題就出現了意想不到的變化。
首先是對集合 Count 屬性的判斷。如果一個集合物件在某個時刻儲存了 10 個弱參考物件,當我們呼叫該集合的 Count 屬性時,返回的值應該是多少?很顯然,我們不能做預先的判斷。事實上,因為弱參考物件的存在,一個本身執行緒安全的呼叫卻出現了並發問題。因為在呼叫 Count 屬性期間,GC 正有可能回收集合中的某些元素物件。
其次是對迭代器的支援。我們知道,在對 IEnumerable 進行 Foreach 遍歷時,不允許我們 Add 或 Remove 集合的元素,否則遍歷就會失敗。如果沒有弱參考物件,一切都是美好的。但在我們遍歷儲存了弱參考物件的集合時,GC 就會像幽靈一般,不知什麼時候鑽出來搗亂,改變集合元素的個數。很顯然,這樣的集合對迭代器的支援是不安全的。
實質上,這兩個問題的本源是相同的,起因就是弱參考的特殊性。如何解決這個問題?一個簡單辦法是定義自己的弱參考集合,對集合物件進行一個簡單的 Wrapper,同時隱藏 Count 屬性,以及其對 IEnumerable 的支援。例如,定義一個弱參考列表:
public sealed class WeakReferenceList<T>
{
private List<WeakReference> m_list;
public void Add(T obj)
{
// 這裡使用短弱參考,一般不建議使用長弱參考;
m_list.Add(new WeakReference(obj));
}
// 其它成員
}
WeakReferenceList 類別並沒有提供 GetEnumerator() 方法的必要,原因如前所述。至於 Count 屬性,我們是否可以透過查詢條件,以過濾那些值為 null 的物件呢?例如:
public int Count
{
get
{
return m_list.Count(e => e.IsAlive);
}
}