當我們在系統用到某些占用內存較多的大對象,且該對象並不會被頻繁使用(例如緩存場景)時,若考慮性能因素,或許我們可以選擇使用弱引用(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 object)
{
//這裡使用短弱引用,一般不建議使用長弱引用;
m_list.Add(new WeakReference(object));
}
//其它成員
}
WeakReferenceList類並沒有提供GetEnumerator()方法的必要,原因如前所述。至於Count屬性,我們是否可以通過查詢條件,以過濾那些值為null的對象呢?例如:
public int Count
{
get
{
return m_list.Count(e => e.IsAlive);
}
}
表面看來這是可行的,可是彼時返回的值,在使用該值的時候未必正確,即使對其進行lock,也無法保證其正確性。與其如此,還不如不提供Count屬性。
剩下一個問題是如何獲得WeakReferenceList的元素?我們需要為其實現特有的索引器。例如:
public T this[int index]
{
get
{
//這裡仍然使用了Count屬性(這說明我們可以定義一個私有屬性Count)
if (index < 0 || index >= this.Count)
{
throw new IndexOutOfRangeException();
}
else
{
if (m_list[index].Target != null)
{
return (T)m_list[index].Target;
}
else
{
throw new Exception("The object is collected by GC.")
}
}
}
}
private int Count
{
get
{
return m_list.Count(e => e.IsAlive);
}
}
更好地管理弱引用對象的集合是HashTable,這也是緩存的一種好的存儲機制。關於HashTable對弱引用對象的應用,請參見Jared Parsons的文章。實際上,本文正是借鑒了該文的思想。