/* Copyright 2008 The 'A Concurrent Hashtable' development team (http://www.codeplex.com/CH/People/ProjectPeople.aspx) This library is licensed under the GNU Library General Public License (LGPL). You should have received a copy of the license along with the source code. If not, an online copy of the license can be found at http://www.codeplex.com/CH/license. */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.Serialization; using System.Collections; using System.Security; #if !SILVERLIGHT using System.Collections.Concurrent; #endif namespace Orvid.Concurrent.Collections { class Slot { public object _Item; public int _GC2WhenLastUsed; } sealed class Level1CacheClass : ConcurrentDictionary, IMaintainable { public Level1CacheClass(IEqualityComparer keyComparer) : base(keyComparer) { _GC2Count = GC.CollectionCount(2); MaintenanceWorker.Register(this); } int _GC2Count; /// /// Table maintenance, removes all items marked as Garbage. /// /// /// A boolean value indicating if the maintenance run could be performed without delay; false if there was a lock on the SyncRoot object /// and the maintenance run could not be performed. /// /// /// This method is called regularly in sync with the garbage collector on a high-priority thread. /// /// This override keeps track of GC.CollectionCount(2) to assess which items have been accessed recently. /// void IMaintainable.DoMaintenance() { int newCount = GC.CollectionCount(2); if (newCount != _GC2Count) { _GC2Count = newCount; Slot dummy; foreach (var kvp in this) if (kvp.Value._GC2WhenLastUsed - _GC2Count < -1) this.TryRemove(kvp.Key, out dummy); } } public bool TryGetItem(TKey key, out object item) { Slot slot; if (base.TryGetValue(key, out slot)) { item = slot._Item; slot._GC2WhenLastUsed = _GC2Count; return true; } item = null; return false; } public bool GetOldestItem(TKey key, ref object item) { var newSlot = new Slot() { _Item = item, _GC2WhenLastUsed = _GC2Count }; var slot = base.GetOrAdd(key, newSlot); item = slot._Item; slot._GC2WhenLastUsed = _GC2Count; return object.ReferenceEquals(newSlot, slot); } } class ObjectComparerClass : IEqualityComparer { public IEqualityComparer _KeyComparer; public new bool Equals(object x, object y) { return x == null ? y == null : (y != null && _KeyComparer.Equals((TKey)x, (TKey)y)); } public int GetHashCode(object obj) { return _KeyComparer.GetHashCode((TKey)obj); } } /// /// Cache; Retains values longer than WeakDictionary /// /// Type of key /// Type of value /// /// Use only for expensive values. /// [Serializable] public sealed class Cache : ISerializable { IEqualityComparer _keyComparer; Level1CacheClass _Level1Cache; WeakDictionary _Level2Cache; /// /// Constructs a new instance of using an explicit of TKey to comparer keys. /// /// The of TKey to compare keys with. public Cache(IEqualityComparer keyComparer) { _keyComparer = keyComparer; _Level1Cache = new Level1CacheClass(keyComparer); _Level2Cache = new WeakDictionary(new ObjectComparerClass { _KeyComparer = keyComparer }, EqualityComparer.Default); } /// /// Constructs a new instance of with the default key comparer. /// public Cache() : this(EqualityComparer.Default) { } Cache(SerializationInfo serializationInfo, StreamingContext streamingContext) { _keyComparer = (IEqualityComparer)serializationInfo.GetValue("Comparer", typeof(IEqualityComparer)); _Level1Cache = new Level1CacheClass(_keyComparer); _Level2Cache = new WeakDictionary(new ObjectComparerClass { _KeyComparer = _keyComparer }, EqualityComparer.Default); foreach (var kvp in (IEnumerable>)serializationInfo.GetValue("Items", typeof(List>))) this.GetOldest(kvp.Key, kvp.Value); } [SecurityCritical] void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Comparer", this._keyComparer); info.AddValue("Level2", _Level1Cache.Select(kvp => new KeyValuePair( kvp.Key, (TValue)kvp.Value._Item ) ).ToList() ); } /// /// Try to retrieve an item from the cache /// /// Key to find the item with. /// Out parameter that will receive the found item. /// A boolean value indicating if an item has been found. public bool TryGetItem(TKey key, out TValue item) { object storedItem; bool found = _Level1Cache.TryGetItem(key, out storedItem); if (!found) { var keyAsObject = (object)key; if (keyAsObject != null && _Level2Cache.TryGetValue(keyAsObject, false, out storedItem)) { found = true; _Level1Cache.GetOldestItem(key, ref storedItem); } } if (found) { item = (TValue)storedItem; return true; } item = default(TValue); return false; } /// /// Tries to find an item in the cache but if it can not be found a new given item will be inserted and returned. /// /// The key to find an existing item or insert a new item with. /// The new item to insert if an existing item can not be found. /// The found item or the newly inserted item. public TValue GetOldest(TKey key, TValue newItem) { object item = (object)newItem; var keyAsObject = (object)key; if (keyAsObject != null) item = _Level2Cache.GetOrAdd(keyAsObject,false, item); _Level1Cache.GetOldestItem(key, ref item); return (TValue)item; } /// /// Clears all entries from the cache. /// public void Clear() { ((ICollection,object>>)_Level2Cache).Clear(); _Level1Cache.Clear(); } } }