有沒有試過從一個集合裡面移除一個對象之後,這個集合仍然留有這個對象?世界之大,無奇不有。稍有疏忽,便會導致這種奇怪的現象。現在讓我們看看這個“不死”對象究竟是怎麼一回事。
1、“不死”對象現身
這個問題起初是我一個同事提出的,為了重現“不死”對象,現把代碼簡化如下:
// Code #01
IList products = new List
products.Add(GetProduct("1412"));
products.Remove(GetProduct("1412"));
其中 Product 類代碼如下:
// Code #02
class Product
{
public Product(string id)
{
m_ID = id;
}
private string m_ID;
public string ID
{
get { return m_ID; }
}
public override string ToString()
{
return "ID: " m_ID;
}
}
而 GetProduct 方法則根據傳入的 ID 從數據庫讀取數據並返回,它的簽名如下:
// Code #03
public static Product GetProduct(string id);
要想知道編號為 1412 的對象是否從 products 中移除,只需在 Code #01 的最後加上這樣一行:
// Code #04
Console.WriteLine(products.Count);
http://www.mscto.com
2、一不小心掉進陷阱
不知道你有沒有查看 SDK 的習慣,其實 SDK 裡面蘊藏著很多對我們解決問題有啟發作用的信息的。現在讓我們看看 SDK 裡面能否找到什麼蛛絲馬跡。
由於 products 的真身是 List
This method determines equality using the default equality comparer EqualityComparer.Default for T, the type of values in the list.
原來,List
The Default property checks whether type T implements the System.IEquatable generic interface and if so returns an EqualityComparer that uses that implementation. Otherwise it returns an EqualityComparer that uses the overrides of Object.Equals and Object.GetHashCode provided by T.
把上面這段話結合 Code #02 來看,我們可以發現 List
由於 GetProduct 方法每次返回的都是一個新的對象(暫時讓我們忘記對象緩存這家伙),於是就導致了集合裡面出現“不死”對象。
3、不要被同一顆子彈打中兩次
“不要被同一顆子彈打中兩次”原意是指同一個錯誤不要兩次犯,這句話暗含著對兩個表示錯誤的對象進行邏輯上的判等,就像上面需要判斷兩個 Product 的對象在邏輯上是否相等那樣。
至此,我們也知道了令 Remove 重新生效的兩個可選辦法是:
讓 Product 類實現 IEquatable
為 Product 類重寫 Equals 和 GetHashCode 方法。
在大多數情況下,我們希望比較的並不是對象的引用,而是對象的內容,與此同時,我們又不太可能為了這些小對象勞師動眾地實現對象緩存,於是,你就很有可能在類似的代碼中邂逅“不死”對象了。
posted on 2007-01-06 22:59 Allen Lee 閱讀(146) 評論(7) 編輯 收藏 引用 網摘 所屬分類: C#(C#培訓 )
評論
# re: 當調用 Remove 失效時 [C#] 2007-01-06 23:06 木野狐
這個問題本質上和集合無關,只是對象的判等問題,應該還是比較容易想得到原因的。 回復 更多評論
# re: 當調用 Remove 失效時 [C#(C#培訓 )] 2007-01-06 23:17 Klesh Wong
同意樓上的,看完頭三行代碼就基本知道是怎麼一回事了.. 回復 更多評論
# re: 當調用 Remove 失效時 [C#] 2007-01-06 23:30 Allen Lee
To 木野狐 and Klesh Wong:
沒錯,這個問題的本質是對象判等,但你不能說它完全與集合無關。假如這裡把 Code #01 的第一行改成:
http://www.mscto.com
IList products = new ArrayList();
那麼將只有重寫 Equals 這個方法才有效了。處理對象判等有很多方法,但不同的集合類可能會采用不同的方法。 回復 更多評論
# re: 當調用 Remove 失效時 [C#] 2007-01-07 00:12 木野狐
@Allen Lee
剛才我用 Reflector 追溯了一下 List
System.Collections.Generic.EqualityComparer
是這個類負責判定對象是否相等。而他有幾個子類如下:
http://www.mscto.com
ByteEqualityComparer,
GenericEqualityComparer
NullableEqualityComparer
ObjectEqualityComparer
上面你提到的如果實現 IEquatable
public override bool Equals(T x, T y)
{
if (x != null)
{
if (y != null)
{
return x.Equals(y);
}
return false;
}
if (y != null)
{
return false;
}
return true;
}
我們可以看到,追根溯源,最終都要調用到 System.Object.Equals 方法的,而這個方法的默認實現就是和 GetHashCode 直接相關。
如果看一下其他幾個判等器(ByteEqualityComparer, NullableEqualityComparer
根據這個分析可以知道,實際上判等和集合類是沒有什麼聯系的,只是集合元素類型自身的比較。並且根據 .Net 的設計規范,Equals 和 == 的語意也應該是一致的。重寫 Equals 語意的同時必然也應該重寫運算符 ==.