??這是一個在使用CacheManager做二級緩存的時候發(fā)現(xiàn)的蛋疼問題。起因是我準備使用這個組件往緩存中存入一個HashSet類型的數(shù)據(jù),該Set擁有一個自定義的Comparer,用來對里面的數(shù)據(jù)做增刪比較。正常情況下,這個Set里面的數(shù)據(jù)不會很多,因為我會在某些場合取出Set進行更新刪除再放回去。但是在測試的時候就出現(xiàn)異常了,存放到里面的數(shù)據(jù)越來越多。
??在排除了未調(diào)用刪除方法后,這個問題就指向了HashSet的Remove方法了。該方法的是通過判斷數(shù)據(jù)的hashcode和equal來區(qū)分兩個數(shù)據(jù)之間是否相等,進而刪除。因為我的數(shù)據(jù)是值類型,也就是說這個自定義的Comparer不起作用了。
??單步調(diào)試后發(fā)現(xiàn),當(dāng)攜帶了Comparer的HashSet存入Redis再取出來后,這個Comparer就變成了默認的比較器,也就是對地址進行比較,導(dǎo)致數(shù)據(jù)無法刪除。那么為什么Comparer會消失掉?
??簡單看了下CacheManager的源碼,發(fā)現(xiàn)在將數(shù)據(jù)存入Redis時,會使用newtonsoft.json先將其序列化,取出時再反序列化回來。這么看來,序列化也許就是主因。再看一下HashSet的源碼,可以看到Comparer屬性是只讀的:
/// <summary>
/// Gets the IEqualityComparer that is used to determine equality of keys for
/// the HashSet.
/// </summary>
public IEqualityComparer<T> Comparer {
get {
return m_comparer;
}
}
m_comparer是私有變量,該變量僅能在構(gòu)造方法中賦值。而HashSet包含了多個帶參的構(gòu)造方法以及一個默認的,帶參的函數(shù)也并沒有使用序列化相關(guān)標記。簡單實驗就能基本了解反序列化原理:當(dāng)無構(gòu)造函數(shù)帶標記時,將使用默認構(gòu)造函數(shù)進行創(chuàng)建;若無默認構(gòu)造函數(shù),將會使用帶參構(gòu)造;若帶參構(gòu)造函數(shù)有多個,將報異常。
以下的Name為無默認構(gòu)造函數(shù)且含多個帶參函數(shù)普通類:
string str = Newtonsoft.Json.JsonConvert.SerializeObject(new Name("first") { LastName = "last" });
var der = Newtonsoft.Json.JsonConvert.DeserializeObject<Name>(str);
以上代碼將報異常:
Unable to find a constructor to use for type Repository.Framework.Name. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute.
由此可知當(dāng)HashSet反序列化時,僅會調(diào)用默認構(gòu)造函數(shù),此時Comparer就被置為默認值了,也就發(fā)生了丟失。
解決方法:
1、當(dāng)從內(nèi)存Get回來后,新建成另外一個HashSet,再操作。
2、不用比較器了,直接使用hashset的RemoveWhere方法。
插曲:
??后續(xù)驗證還發(fā)現(xiàn),當(dāng)某個key第一次存進去再取出來后,HashSet的Comparer依然存在;當(dāng)更新后,就變回了默認值.....真是個騷操作?;仡^再看了以下CacheManager源碼,發(fā)現(xiàn)在使用SystemRuntimeCache緩存時,他并不會序列化數(shù)據(jù),而我剛好做了多級緩存,有一個就是SystemRuntimeCache。并且調(diào)整了一下配置的順序后,這個現(xiàn)象就消失了。所以猜測和CacheManager內(nèi)部機制有關(guān),就不再深入了研究了。