namespace RSn9.Caching { using System; using System.Collections; using System.Collections.Generic; using System.Timers; /// MRW: 19/02/07 public sealed class SimpleCache : IDictionary, ICollection>, ICollection, IEnumerable>, IEnumerable, IDisposable { private TimeSpan _commonSlide = TimeSpan.FromMinutes(5); private TimeSpan _commonExpire = TimeSpan.FromMinutes(15); private Dictionary> _data; private Timer _timer; /// /// Gets or sets the with the specified key. /// /// /// MRW: 29/02/2006 public T this[string key] { get { lock( ( (ICollection)this._data ).SyncRoot ) { if ( this._data[key].Slide() ) { this.Remove( key ); return default(T); } else { return this._data[key]._data; } } } set { lock( ( (ICollection)this._data ).SyncRoot ) { if ( this._data[key].Slide() ) this.Remove( key ); else this._data[key]._data = value; } } } #region (de)construction /// /// Initializes a new instance of the class. /// /// MRW: 29/02/2006 public SimpleCache() : this(4, TimeSpan.FromSeconds(5)) {} /// /// Initializes a new instance of the class. /// /// The capacity. /// MRW: 29/02/2006 public SimpleCache(int capacity) : this(capacity, TimeSpan.FromSeconds(5)) {} /// /// Initializes a new instance of the class. /// /// The interval. /// MRW: 29/02/2006 public SimpleCache( TimeSpan interval ) : this( 4, interval ) { } /// /// Initializes a new instance of the class. /// /// MRW: 29/02/2006 public SimpleCache(int capacity, TimeSpan interval) { this._data = new Dictionary>(capacity); this._timer = new Timer(); this._timer.AutoReset = true; this._timer.Interval = interval.TotalMilliseconds; this._timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed); this._timer.Start(); } /// /// Releases unmanaged resources and performs other cleanup operations before the /// is reclaimed by garbage collection. /// /// MRW: 29/02/2006 ~SimpleCache() { this.Dispose(false); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// MRW: 29/02/2006 public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// /// Disposes the specified disposing. /// /// if set to true [disposing]. /// MRW: 29/02/2006 private void Dispose(bool disposing) { if( disposing ) { this.RemoveAll(); if( this._timer != null ) { this._timer.Dispose(); this._timer = null; } } } #endregion #region Additive /// /// Adds an element with the provided key and value to the . /// /// The object to use as the key of the element to add. /// The object to use as the value of the element to add. /// The is read-only. /// An element with the same key already exists in the . /// key is null. /// MRW: 29/02/2006 public void Add(string key, T value) { lock( ( (ICollection)this._data ).SyncRoot ) this._data.Add(key, new entry(value, this._commonExpire, this._commonSlide, null)); } /// /// Adds the specified key. /// /// The key. /// The value. /// The expire. /// MRW: 29/02/2006 public void Add(string key, T value, TimeSpan expire) { lock( ( (ICollection)this._data ).SyncRoot ) this._data.Add(key, new entry(value, expire, this._commonSlide, null)); } /// /// Adds the specified key. /// /// The key. /// The value. /// The expire. /// The slide. /// MRW: 29/02/2006 public void Add(string key, T value, TimeSpan expire, TimeSpan slide) { lock( ( (ICollection)this._data ).SyncRoot ) this._data.Add(key, new entry(value, expire, slide, null)); } /// /// Adds the specified key. /// /// The key. /// The value. /// The expire handle. /// MRW: 29/02/2006 public void Add(string key, T value, EventHandler expireHandle) { lock( ( (ICollection)this._data ).SyncRoot ) this._data.Add(key, new entry(value, this._commonExpire, this._commonSlide, expireHandle)); } /// /// Adds the specified key. /// /// The key. /// The value. /// The expire. /// The expire handle. /// MRW: 29/02/2006 public void Add(string key, T value, TimeSpan expire, EventHandler expireHandle) { lock( ( (ICollection)this._data ).SyncRoot ) this._data.Add(key, new entry(value, expire, this._commonSlide, expireHandle)); } /// /// Adds the specified key. /// /// The key. /// The value. /// The expire. /// The slide. /// The expire handle. /// MRW: 29/02/2006 public void Add(string key, T value, TimeSpan expire, TimeSpan slide, EventHandler expireHandle) { lock( ( (ICollection)this._data ).SyncRoot ) this._data.Add(key, new entry(value, expire, slide, expireHandle)); } #endregion #region Subtractive /// /// Forces the expire. /// /// MRW: 29/02/2006 public void ForceExpire() { lock( ( (ICollection)this._data ).SyncRoot ) try { this._timer.Stop(); this.InternalExpire(true); } finally { this._timer.Start(); } } /// /// Removes the element with the specified key from the . /// /// The key of the element to remove. /// /// true if the element is successfully removed; otherwise, false. This method also returns false if key was not found in the original . /// /// The is read-only. /// key is null. /// MRW: 29/02/2006 public bool Remove(string key) { lock( ( (ICollection)this._data ).SyncRoot ) { if( this._data[key]._onExpire != null ) this._data[key]._onExpire(this._data[key]._data, EventArgs.Empty); this._data[key].Dispose(); return this._data.Remove(key); } } /// /// Removes all. /// /// MRW: 29/02/2006 public void RemoveAll() { if( this._data.Count > 0 ) lock( ( (ICollection)this._data ).SyncRoot ) { foreach( string key in this._data.Keys ) this.Remove(key); this._data.Clear(); } } #endregion #region Discriptive /// /// Determines whether the contains an element with the specified key. /// /// The key to locate in the . /// /// true if the contains an element with the key; otherwise, false. /// /// key is null. /// MRW: 29/02/2006 public bool ContainsKey(string key) { lock( ( (ICollection)this._data ).SyncRoot ) return this._data.ContainsKey(key); } /// /// Gets the number of elements contained in the . /// /// /// The number of elements contained in the . /// MRW: 29/02/2006 public int Count { get { lock( ( (ICollection)this._data ).SyncRoot ) return this._data.Count; } } /// /// Gets a value indicating whether the is read-only. /// /// /// true if the is read-only; otherwise, false. /// MRW: 29/02/2006 public bool IsReadOnly { get { return false; } } /// /// Gets a value indicating whether access to the is synchronized (thread safe). /// /// /// true if access to the is synchronized (thread safe); otherwise, false. /// MRW: 29/02/2006 public bool IsSynchronized { get { return true; } } #endregion #region operative /// /// Removes all items from the . /// /// The is read-only. /// MRW: 29/02/2006 public void Clear() { lock( ( (ICollection)this._data ).SyncRoot ) { this._data.Clear(); } } /// /// Copies the elements of the to an , starting at a particular index. /// /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. /// The zero-based index in array at which copying begins. /// array is null. /// index is less than zero. /// array is multidimensional.-or- index is equal to or greater than the length of array.-or- The number of elements in the source is greater than the available space from index to the end of the destination array. /// The type of the source cannot be cast automatically to the type of the destination array. /// MRW: 29/02/2006 public void CopyTo(Array array, int index) { ( (ICollection)this._data ).CopyTo(array, index); } /// /// Gets an containing the keys of the . /// /// /// An containing the keys of the object that implements . /// MRW: 29/02/2006 public ICollection Keys { get { lock( ( (ICollection)this._data ).SyncRoot ) return this._data.Keys; } } /// /// Gets a COPY of containing the values in the . /// /// /// An containing the values in the object that implements . /// MRW: 29/02/2006 public ICollection Values { get { lock( ( (ICollection)this._data ).SyncRoot ) { Dictionary>.ValueCollection rr = this._data.Values; List lst = new List(rr.Count); foreach( entry en in rr ) lst.Add(en._data); return lst; } } } /// /// Gets an object that can be used to synchronize access to the . /// /// /// An object that can be used to synchronize access to the . /// MRW: 29/02/2006 public object SyncRoot { get { return true; } } /// /// Returns an enumerator that iterates through the cache. /// /// /// An object that can be used to iterate through the collection. /// /// MRW: 29/02/2006 public IEnumerator GetEnumerator() { lock( ( (ICollection)this._data ).SyncRoot ) return this._data.GetEnumerator(); } /// /// Not implemented. /// /// The key. /// The value. /// /// MRW: 29/02/2006 bool IDictionary.TryGetValue(string key, out T value) { throw new NotImplementedException(); } #endregion #region KeyValuePair based /// /// Adds an item to the . /// /// The object to add to the . /// The is read-only. /// MRW: 29/02/2006 public void Add(KeyValuePair item) { this.Add(item.Key, item.Value); } /// /// Adds an item to the . /// /// The object to add to the . /// The is read-only. /// MRW: 29/02/2006 void ICollection>.Add(KeyValuePair item) { this.Add(item.Key, item.Value); } /// /// Determines whether the contains a specific value. /// /// The object to locate in the . /// /// true if item is found in the ; otherwise, false. /// /// MRW: 29/02/2006 bool ICollection>.Contains(KeyValuePair item) { lock( ( (ICollection)this._data ).SyncRoot ) { Dictionary tmp = new Dictionary(this._data.Count); foreach( string key in this._data.Keys ) tmp.Add(key, this._data[key]._data); return ( (ICollection>)tmp ).Contains(item); } } /// /// Copies the elements of the to an , starting at a particular index. /// /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. /// The zero-based index in array at which copying begins. /// arrayIndex is less than 0. /// array is null. /// array is multidimensional.-or-arrayIndex is equal to or greater than the length of array.-or-The number of elements in the source is greater than the available space from arrayIndex to the end of the destination array.-or-Type T cannot be cast automatically to the type of the destination array. /// MRW: 29/02/2006 void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { lock( ( (ICollection)this._data ).SyncRoot ) { Dictionary tmp = new Dictionary(this._data.Count); foreach( string key in this._data.Keys ) tmp.Add(key, this._data[key]._data); ( (ICollection>)tmp ).CopyTo(array, arrayIndex); } } /// /// Removes the first occurrence of a specific object from the . /// /// The object to remove from the . /// /// true if item was successfully removed from the ; otherwise, false. This method also returns false if item is not found in the original . /// /// The is read-only. /// MRW: 29/02/2006 bool ICollection>.Remove(KeyValuePair item) { return this.Remove(item.Key); } /// /// Returns an enumerator that iterates through the collection. /// /// /// A that can be used to iterate through the collection. /// /// MRW: 29/02/2006 IEnumerator> IEnumerable>.GetEnumerator() { lock( ( (ICollection)this._data ).SyncRoot ) { Dictionary tmp = new Dictionary(this._data.Count); foreach( string key in this._data.Keys ) tmp.Add(key, this._data[key]._data); return tmp.GetEnumerator(); } } #endregion #region internal implementation /// /// Handles the Elapsed event of the _timer control. /// /// The source of the event. /// The instance containing the event data. /// MRW: 29/02/2006 private void _timer_Elapsed(object sender, ElapsedEventArgs e) { lock( ( (ICollection)this._data ).SyncRoot ) try { this._timer.Stop(); this.InternalExpire(false); } finally { this._timer.Start(); } } /// /// Internally runs the expiry. /// /// if set to true [force]. /// MRW: 29/02/2006 private void InternalExpire(bool force) { if( this._data.Count > 0 ) { List killList = new List(); DateTime now = DateTime.Now; foreach( string key in this._data.Keys ) { entry tmp = this._data[key]; if( tmp._expire < now && ( force || tmp._allowExpire ) ) killList.Add(key); } foreach( string key in killList ) { if( this._data[key]._onExpire != null ) this._data[key]._onExpire(this._data[key]._data, EventArgs.Empty); this._data[key].Dispose(); this._data.Remove(key); } } } #endregion #region entry /// /// Represents a cache entry and all of its associated data /// /// Anything... literally /// MRW: 29/02/2006 private sealed class entry : IDisposable { public U _data; public DateTime _stamp; public TimeSpan _slide; public DateTime _expire; public bool _allowExpire; public EventHandler _onExpire; public bool Slide() { DateTime now = DateTime.Now; if ( now < this._expire ) { if ( now + this._slide > this._expire ) this._expire = this._expire.Add( this._slide ); return false; } return true; // expire NOW! do not pass go... } /// /// Initializes a new instance of the class. /// /// The data. /// The expire. /// The slide. /// The expire handler. /// MRW: 29/02/2006 public entry(U data, TimeSpan expire, TimeSpan slide, EventHandler expireHandler) { this._data = data; this._stamp = DateTime.Now; this._slide = slide; if ( expire == TimeSpan.Zero ) this._allowExpire = false; else if ( expire == TimeSpan.MaxValue ) { this._expire = this._stamp.Add( this._slide ); this._allowExpire = true; } else { this._allowExpire = true; this._expire = this._stamp.Add( expire ); } this._onExpire = expireHandler; } /// /// Releases unmanaged resources and performs other cleanup operations before the /// is reclaimed by garbage collection. /// /// MRW: 29/02/2006 ~entry() { this.Dispose(false); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// MRW: 29/02/2006 public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// /// Disposes the specified disposing. /// /// if set to true [disposing]. /// MRW: 29/02/2006 private void Dispose(bool disposing) { if( disposing ) if( this._data is IDisposable ) ( this._data as IDisposable ).Dispose(); } } #endregion } }