2009-06-04 01:09:18 +00:00
/ *
* Copyright ( c ) Contributors , http : //opensimulator.org/
* See CONTRIBUTORS . TXT for a full list of copyright holders .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
* * Redistributions of source code must retain the above copyright
* notice , this list of conditions and the following disclaimer .
* * Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution .
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission .
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ` ` AS IS ' ' AND ANY
* EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
* ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ;
* LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
* ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
* /
2009-06-04 00:51:02 +00:00
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.Diagnostics ;
namespace OpenSim.Framework
{
/// <summary>
/// Cenome memory based cache to store key/value pairs (elements) limited time and/or limited size.
/// </summary>
/// <typeparam name="TKey">
/// The type of keys in the cache.
/// </typeparam>
/// <typeparam name="TValue">
/// The type of values in the dictionary.
/// </typeparam>
/// <remarks>
/// <para>
/// Cenome memory cache stores elements to hash table generations. When new element is being added to cache, and new size would exceed
/// maximal allowed size or maximal amount of allowed element count, then elements in oldest generation are deleted. Last access time
/// is also tracked in generation level - thus it is possible that some elements are staying in cache far beyond their expiration time.
/// If elements in older generations are accessed through <see cref="TryGetValue"/> method, they are moved to newest generation.
/// </para>
/// </remarks>
public class CnmMemoryCache < TKey , TValue > : ICnmCache < TKey , TValue >
{
/// <summary>
/// Default maximal count.
/// </summary>
/// <seealso cref="MaxCount"/>
public const int DefaultMaxCount = 4096 ;
/// <summary>
/// Default maximal size.
/// </summary>
/// <remarks>
/// <para>
/// 128MB = 128 * 1024^2 = 134 217 728 bytes.
/// </para>
/// </remarks>
/// <seealso cref="MaxSize"/>
public const long DefaultMaxSize = 134217728 ;
/// <summary>
/// How many operations between time checks.
/// </summary>
private const int DefaultOperationsBetweenTimeChecks = 40 ;
/// <summary>
/// Default expiration time.
/// </summary>
/// <remarks>
/// <para>
/// 30 minutes.
/// </para>
/// </remarks>
public static readonly TimeSpan DefaultExpirationTime = TimeSpan . FromMinutes ( 30.0 ) ;
/// <summary>
/// Minimal allowed expiration time.
/// </summary>
/// <remarks>
/// <para>
/// 5 minutes.
/// </para>
/// </remarks>
public static readonly TimeSpan MinExpirationTime = TimeSpan . FromSeconds ( 10.0 ) ;
/// <summary>
/// Comparer used to compare element keys.
/// </summary>
/// <remarks>
/// Comparer is initialized by constructor.
/// </remarks>
/// <seealso cref="CnmMemoryCache{TKey,TValue}"/>
public readonly IEqualityComparer < TKey > Comparer ;
/// <summary>
/// Expiration time.
/// </summary>
private TimeSpan m_expirationTime = DefaultExpirationTime ;
/// <summary>
/// Generation bucket count.
/// </summary>
private int m_generationBucketCount ;
/// <summary>
/// Generation entry count.
/// </summary>
private int m_generationElementCount ;
/// <summary>
/// Generation max size.
/// </summary>
private long m_generationMaxSize ;
/// <summary>
/// Maximal allowed count of elements.
/// </summary>
private int m_maxCount ;
/// <summary>
/// Maximal allowed total size of elements.
/// </summary>
private long m_maxElementSize ;
/// <summary>
/// Maximal size.
/// </summary>
private long m_maxSize ;
/// <summary>
/// New generation.
/// </summary>
private IGeneration m_newGeneration ;
/// <summary>
/// Old generation.
/// </summary>
private IGeneration m_oldGeneration ;
/// <summary>
/// Operations between time check.
/// </summary>
private int m_operationsBetweenTimeChecks = DefaultOperationsBetweenTimeChecks ;
/// <summary>
/// Synchronization root object, should always be private and exists always
/// </summary>
private readonly object m_syncRoot = new object ( ) ;
/// <summary>
/// Version of cache.
/// </summary>
/// <remarks>
/// <para>
/// Updated every time when cache has been changed (element removed, expired, added, replaced).
/// </para>
/// </remarks>
private int m_version ;
/// <summary>
/// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class.
/// </summary>
public CnmMemoryCache ( )
: this ( DefaultMaxSize )
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class.
/// </summary>
/// <param name="maximalSize">
/// Maximal cache size.
/// </param>
public CnmMemoryCache ( long maximalSize )
: this ( maximalSize , DefaultMaxCount )
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class.
/// </summary>
/// <param name="maximalSize">
/// Maximal cache size.
/// </param>
/// <param name="maximalCount">
/// Maximal element count.
/// </param>
public CnmMemoryCache ( long maximalSize , int maximalCount )
: this ( maximalSize , maximalCount , DefaultExpirationTime )
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class.
/// </summary>
/// <param name="maximalSize">
/// Maximal cache size.
/// </param>
/// <param name="maximalCount">
/// Maximal element count.
/// </param>
/// <param name="expirationTime">
/// Elements expiration time.
/// </param>
public CnmMemoryCache ( long maximalSize , int maximalCount , TimeSpan expirationTime )
: this ( maximalSize , maximalCount , expirationTime , EqualityComparer < TKey > . Default )
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CnmMemoryCache{TKey,TValue}"/> class.
/// </summary>
/// <param name="maximalSize">
/// Maximal cache size.
/// </param>
/// <param name="maximalCount">
/// Maximal element count.
/// </param>
/// <param name="expirationTime">
/// Elements expiration time.
/// </param>
/// <param name="comparer">
/// Comparer used for comparing elements.
/// </param>
/// <exception cref="ArgumentNullException">
/// <see cref="comparer"/>is <see langword="null"/> reference.
/// </exception>
public CnmMemoryCache ( long maximalSize ,
int maximalCount ,
TimeSpan expirationTime ,
IEqualityComparer < TKey > comparer )
{
if ( comparer = = null )
throw new ArgumentNullException ( "comparer" ) ;
if ( expirationTime < MinExpirationTime )
expirationTime = MinExpirationTime ;
if ( maximalCount < 8 )
maximalCount = 8 ;
if ( maximalSize < 8 )
maximalSize = 8 ;
if ( maximalCount > maximalSize )
maximalCount = ( int ) maximalSize ;
Comparer = comparer ;
m_expirationTime = expirationTime ;
m_maxSize = maximalSize ;
m_maxCount = maximalCount ;
Initialize ( ) ;
}
/// <summary>
/// Add element to new generation.
/// </summary>
/// <param name="bucketIndex">
/// The bucket index.
/// </param>
/// <param name="key">
/// The element's key.
/// </param>
/// <param name="value">
/// The element's value.
/// </param>
/// <param name="size">
/// The element's size.
/// </param>
protected virtual void AddToNewGeneration ( int bucketIndex , TKey key , TValue value , long size )
{
// Add to newest generation
if ( ! m_newGeneration . Set ( bucketIndex , key , value , size ) )
{
// Failed to add new generation
RecycleGenerations ( ) ;
m_newGeneration . Set ( bucketIndex , key , value , size ) ;
}
m_version + + ;
}
/// <summary>
/// <para>
/// Get keys bucket index.
/// </para>
/// </summary>
/// <param name="key">
/// <para>
/// Key which bucket index is being retrieved.
/// </para>
/// </param>
/// <returns>
/// <para>
/// Bucket index.
/// </para>
/// </returns>
/// <remarks>
/// <para>
/// Method uses <see cref="Comparer"/> to calculate <see cref="key"/> hash code.
/// </para>
/// <para>
/// Bucket index is remainder when element key's hash value is divided by bucket count.
/// </para>
/// <para>
/// For example: key's hash is 72, bucket count is 5, element's bucket index is 72 % 5 = 2.
/// </para>
/// </remarks>
protected virtual int GetBucketIndex ( TKey key )
{
return ( Comparer . GetHashCode ( key ) & 0x7FFFFFFF ) % m_generationBucketCount ;
}
/// <summary>
/// Purge generation from the cache.
/// </summary>
/// <param name="generation">
/// The generation that is purged.
/// </param>
protected virtual void PurgeGeneration ( IGeneration generation )
{
generation . Clear ( ) ;
m_version + + ;
}
/// <summary>
/// check expired.
/// </summary>
private void CheckExpired ( )
{
// Do this only one in every m_operationsBetweenTimeChecks
// Fetching time is using several millisecons - it is better not to do all time.
m_operationsBetweenTimeChecks - - ;
if ( m_operationsBetweenTimeChecks < = 0 )
PurgeExpired ( ) ;
}
/// <summary>
/// Initialize cache.
/// </summary>
private void Initialize ( )
{
m_version + + ;
m_generationMaxSize = MaxSize / 2 ;
MaxElementSize = MaxSize / 8 ;
m_generationElementCount = MaxCount / 2 ;
// Buckets need to be prime number to get better spread of hash values
m_generationBucketCount = PrimeNumberHelper . GetPrime ( m_generationElementCount ) ;
m_newGeneration = new HashGeneration ( this ) ;
m_oldGeneration = new HashGeneration ( this ) ;
m_oldGeneration . MakeOld ( ) ;
}
/// <summary>
/// Recycle generations.
/// </summary>
private void RecycleGenerations ( )
{
// Rotate old generation to new generation, new generation to old generation
IGeneration temp = m_newGeneration ;
m_newGeneration = m_oldGeneration ;
m_newGeneration . Clear ( ) ;
m_oldGeneration = temp ;
m_oldGeneration . MakeOld ( ) ;
}
#region Nested type: Enumerator
/// <summary>
/// Key and value pair enumerator.
/// </summary>
private class Enumerator : IEnumerator < KeyValuePair < TKey , TValue > >
{
/// <summary>
/// Current enumerator.
/// </summary>
private int m_currentEnumerator = - 1 ;
/// <summary>
/// Enumerators to different generations.
/// </summary>
private readonly IEnumerator < KeyValuePair < TKey , TValue > > [ ] m_generationEnumerators =
new IEnumerator < KeyValuePair < TKey , TValue > > [ 2 ] ;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> class.
/// </summary>
/// <param name="cache">
/// The cache.
/// </param>
public Enumerator ( CnmMemoryCache < TKey , TValue > cache )
{
m_generationEnumerators [ 0 ] = cache . m_newGeneration . GetEnumerator ( ) ;
m_generationEnumerators [ 1 ] = cache . m_oldGeneration . GetEnumerator ( ) ;
}
#region IEnumerator<KeyValuePair<TKey,TValue>> Members
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The enumerator has reach end of collection or <see cref="MoveNext"/> is not called.
/// </exception>
public KeyValuePair < TKey , TValue > Current
{
get
{
if ( m_currentEnumerator = = - 1 | | m_currentEnumerator > = m_generationEnumerators . Length )
throw new InvalidOperationException ( ) ;
return m_generationEnumerators [ m_currentEnumerator ] . Current ;
}
}
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">
/// The enumerator is positioned before the first element of the collection or after the last element.
/// </exception><filterpriority>2</filterpriority>
object IEnumerator . Current
{
get { return Current ; }
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose ( )
{
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// <see langword="true"/>if the enumerator was successfully advanced to the next element; <see langword="false"/> if the enumerator has passed the end of the collection.
/// </returns>
/// <exception cref="T:System.InvalidOperationException">
/// The collection was modified after the enumerator was created.
/// </exception>
/// <filterpriority>2</filterpriority>
public bool MoveNext ( )
{
if ( m_currentEnumerator = = - 1 )
m_currentEnumerator = 0 ;
while ( m_currentEnumerator < m_generationEnumerators . Length )
{
if ( m_generationEnumerators [ m_currentEnumerator ] . MoveNext ( ) )
return true ;
m_currentEnumerator + + ;
}
return false ;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="T:System.InvalidOperationException">
/// The collection was modified after the enumerator was created.
/// </exception>
/// <filterpriority>2</filterpriority>
public void Reset ( )
{
foreach ( IEnumerator < KeyValuePair < TKey , TValue > > enumerator in m_generationEnumerators )
{
enumerator . Reset ( ) ;
}
m_currentEnumerator = - 1 ;
}
# endregion
}
# endregion
#region Nested type: HashGeneration
/// <summary>
/// Hash generation class
/// </summary>
/// <remarks>
/// <para>
/// Current implementation is based to separated chaining with move-to-front heuristics. Hash generations have fixed
/// amount of buckets and it is never rehashed.
/// </para>
/// <para>
/// Read more about hash tables from <a href="http://en.wikipedia.org/wiki/Hash_table">Wiki article</a>.
/// </para>
/// </remarks>
/// <seealso href="http://en.wikipedia.org/wiki/Hash_table"/>
private class HashGeneration : IGeneration
{
/// <summary>
/// Value indicating whether generation was accessed since last time check.
/// </summary>
private bool m_accessedSinceLastTimeCheck ;
/// <summary>
/// Index of first element's in element chain.
/// </summary>
/// <value>
/// -1 if there is no element in bucket; otherwise first element's index in the element chain.
/// </value>
/// <remarks>
/// Bucket index is remainder when element key's hash value is divided by bucket count.
/// For example: key's hash is 72, bucket count is 5, element's bucket index is 72 % 5 = 2.
/// </remarks>
private readonly int [ ] m_buckets ;
/// <summary>
/// Cache object.
/// </summary>
private readonly CnmMemoryCache < TKey , TValue > m_cache ;
/// <summary>
/// Generation's element array.
/// </summary>
/// <seealso cref="Element"/>
private readonly Element [ ] m_elements ;
/// <summary>
/// Generation's expiration time.
/// </summary>
private DateTime m_expirationTime1 ;
/// <summary>
/// Index to first free element.
/// </summary>
private int m_firstFreeElement ;
/// <summary>
/// Free element count.
/// </summary>
/// <remarks>
/// When generation is cleared or constructed, this is NOT set to element count.
/// This is only tracking elements that are removed and are currently free.
/// </remarks>
private int m_freeCount ;
/// <summary>
/// Is this generation "new generation".
/// </summary>
private bool m_newGeneration ;
/// <summary>
/// Next unused entry.
/// </summary>
private int m_nextUnusedElement ;
/// <summary>
/// Size of data stored to generation.
/// </summary>
private long m_size ;
/// <summary>
/// Initializes a new instance of the <see cref="HashGeneration"/> class.
/// </summary>
/// <param name="cache">
/// The cache.
/// </param>
public HashGeneration ( CnmMemoryCache < TKey , TValue > cache )
{
m_cache = cache ;
m_elements = new Element [ m_cache . m_generationElementCount ] ;
m_buckets = new int [ m_cache . m_generationBucketCount ] ;
Clear ( ) ;
}
/// <summary>
/// Find element's index
/// </summary>
/// <param name="bucketIndex">
/// The element's bucket index.
/// </param>
/// <param name="key">
/// The element's key.
/// </param>
/// <param name="moveToFront">
/// Move element to front of elements.
/// </param>
/// <param name="previousIndex">
/// The previous element's index.
/// </param>
/// <returns>
/// Element's index, if found from the generation; -1 otherwise (if element is not found the generation).
/// </returns>
private int FindElementIndex ( int bucketIndex , TKey key , bool moveToFront , out int previousIndex )
{
previousIndex = - 1 ;
int elementIndex = m_buckets [ bucketIndex ] ;
while ( elementIndex > = 0 )
{
if ( m_cache . Comparer . Equals ( key , m_elements [ elementIndex ] . Key ) )
{
// Found match
if ( moveToFront & & previousIndex > = 0 )
{
// Move entry to front
m_elements [ previousIndex ] . Next = m_elements [ elementIndex ] . Next ;
m_elements [ elementIndex ] . Next = m_buckets [ bucketIndex ] ;
m_buckets [ bucketIndex ] = elementIndex ;
previousIndex = 0 ;
}
return elementIndex ;
}
previousIndex = elementIndex ;
elementIndex = m_elements [ elementIndex ] . Next ;
}
return - 1 ;
}
/// <summary>
/// Remove element front the generation.
/// </summary>
/// <param name="bucketIndex">
/// The bucket index.
/// </param>
/// <param name="entryIndex">
/// The element index.
/// </param>
/// <param name="previousIndex">
/// The element's previous index.
/// </param>
private void RemoveElement ( int bucketIndex , int entryIndex , int previousIndex )
{
if ( previousIndex > = 0 )
m_elements [ previousIndex ] . Next = m_elements [ entryIndex ] . Next ;
else
m_buckets [ bucketIndex ] = m_elements [ entryIndex ] . Next ;
Size - = m_elements [ entryIndex ] . Size ;
m_elements [ entryIndex ] . Value = default ( TValue ) ;
m_elements [ entryIndex ] . Key = default ( TKey ) ;
// Add element to free elements list
m_elements [ entryIndex ] . Next = m_firstFreeElement ;
m_firstFreeElement = entryIndex ;
m_freeCount + + ;
}
#region Nested type: Element
/// <summary>
/// Element that stores key, next element in chain, size and value.
/// </summary>
private struct Element
{
/// <summary>
/// Element's key.
/// </summary>
public TKey Key ;
/// <summary>
/// Next element in chain.
/// </summary>
/// <remarks>
/// When element have value (something is stored to it), this is index of
/// next element with same bucket index. When element is free, this
/// is index of next element in free element's list.
/// </remarks>
public int Next ;
/// <summary>
/// Size of element.
/// </summary>
/// <value>
/// 0 if element is free; otherwise larger than 0.
/// </value>
public long Size ;
/// <summary>
/// Element's value.
/// </summary>
/// <remarks>
/// It is possible that this value is <see langword="null"/> even when element
/// have value - element's value is then <see langword="null"/> reference.
/// </remarks>
public TValue Value ;
/// <summary>
/// Gets a value indicating whether element is free or have value.
/// </summary>
/// <value>
/// <see langword="true"/> when element is free; otherwise <see langword="false"/>.
/// </value>
public bool IsFree
{
get { return Size = = 0 ; }
}
}
# endregion
#region Nested type: Enumerator
/// <summary>
/// Key value pair enumerator for <see cref="HashGeneration"/> object.
/// </summary>
private class Enumerator : IEnumerator < KeyValuePair < TKey , TValue > >
{
/// <summary>
/// Current element.
/// </summary>
private KeyValuePair < TKey , TValue > m_current ;
/// <summary>
/// Current index.
/// </summary>
private int m_currentIndex ;
/// <summary>
/// Generation that is being enumerated.
/// </summary>
private readonly HashGeneration m_generation ;
/// <summary>
/// Cache version.
/// </summary>
/// <remarks>
/// When cache is change, version number is changed.
/// </remarks>
/// <seealso cref="CnmMemoryCache{TKey,TValue}.m_version"/>
private readonly int m_version ;
/// <summary>
/// Initializes a new instance of the <see cref="Enumerator"/> class.
/// </summary>
/// <param name="generation">
/// The generation.
/// </param>
public Enumerator ( HashGeneration generation )
{
m_generation = generation ;
m_version = m_generation . m_cache . m_version ;
}
#region IEnumerator<KeyValuePair<TKey,TValue>> Members
/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
/// </summary>
/// <returns>
/// The element in the collection at the current position of the enumerator.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The enumerator has reach end of collection or <see cref="MoveNext"/> is not called.
/// </exception>
public KeyValuePair < TKey , TValue > Current
{
get
{
if ( m_currentIndex = = 0 | | m_currentIndex > = m_generation . Count )
throw new InvalidOperationException ( ) ;
return m_current ;
}
}
/// <summary>
/// Gets the current element in the collection.
/// </summary>
/// <returns>
/// The current element in the collection.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The enumerator is positioned before the first element of the collection or after the last element.
/// </exception><filterpriority>2</filterpriority>
object IEnumerator . Current
{
get { return Current ; }
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <filterpriority>2</filterpriority>
public void Dispose ( )
{
}
/// <summary>
/// Advances the enumerator to the next element of the collection.
/// </summary>
/// <returns>
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The collection was modified after the enumerator was created.
/// </exception>
public bool MoveNext ( )
{
if ( m_version ! = m_generation . m_cache . m_version )
throw new InvalidOperationException ( ) ;
while ( m_currentIndex < m_generation . Count )
{
if ( m_generation . m_elements [ m_currentIndex ] . IsFree )
{
m_currentIndex + + ;
continue ;
}
m_current = new KeyValuePair < TKey , TValue > ( m_generation . m_elements [ m_currentIndex ] . Key ,
m_generation . m_elements [ m_currentIndex ] . Value ) ;
m_currentIndex + + ;
return true ;
}
m_current = new KeyValuePair < TKey , TValue > ( ) ;
return false ;
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the first element in the collection.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The collection was modified after the enumerator was created.
/// </exception>
/// <filterpriority>2</filterpriority>
public void Reset ( )
{
if ( m_version ! = m_generation . m_cache . m_version )
throw new InvalidOperationException ( ) ;
m_currentIndex = 0 ;
}
# endregion
}
# endregion
#region IGeneration Members
/// <summary>
/// Gets or sets a value indicating whether generation was accessed since last time check.
/// </summary>
public bool AccessedSinceLastTimeCheck
{
get { return m_accessedSinceLastTimeCheck ; }
set { m_accessedSinceLastTimeCheck = value ; }
}
/// <summary>
/// Gets element count in generation.
/// </summary>
public int Count
{
get { return m_nextUnusedElement - m_freeCount ; }
}
/// <summary>
/// Gets or sets generation's expiration time.
/// </summary>
public DateTime ExpirationTime
{
get { return m_expirationTime1 ; }
set { m_expirationTime1 = value ; }
}
/// <summary>
/// Gets or sets size of data stored to generation.
/// </summary>
public long Size
{
get { return m_size ; }
private set { m_size = value ; }
}
/// <summary>
/// Clear all elements from the generation and make generation new again.
/// </summary>
/// <remarks>
/// When generation is new, it is allowed to add new elements to it and
/// <see cref="IGeneration.TryGetValue"/>doesn't remove elements from it.
/// </remarks>
/// <seealso cref="IGeneration.MakeOld"/>
public void Clear ( )
{
for ( int i = m_buckets . Length - 1 ; i > = 0 ; i - - )
{
m_buckets [ i ] = - 1 ;
}
Array . Clear ( m_elements , 0 , m_elements . Length ) ;
Size = 0 ;
m_firstFreeElement = - 1 ;
m_freeCount = 0 ;
m_nextUnusedElement = 0 ;
m_newGeneration = true ;
ExpirationTime = DateTime . MaxValue ;
}
/// <summary>
/// Determines whether the <see cref="IGeneration"/> contains an element with the specific key.
/// </summary>
/// <param name="bucketIndex">
/// The bucket index for the <see cref="key"/> to locate in <see cref="IGeneration"/>.
/// </param>
/// <param name="key">
/// The key to locate in the <see cref="IGeneration"/>.
/// </param>
/// <returns>
/// <see langword="true"/>if the <see cref="IGeneration"/> contains an element with the <see cref="key"/>;
/// otherwise <see langword="false"/>.
/// </returns>
public bool Contains ( int bucketIndex , TKey key )
{
int previousIndex ;
if ( FindElementIndex ( bucketIndex , key , true , out previousIndex ) = = - 1 )
return false ;
AccessedSinceLastTimeCheck = true ;
return true ;
}
/// <summary>
/// Returns an enumerator that iterates through the elements stored <see cref="HashGeneration"/>.
/// </summary>
/// <returns>
/// A <see cref="IEnumerator"/> that can be used to iterate through the <see cref="HashGeneration"/>.
/// </returns>
/// <filterpriority>1</filterpriority>
public IEnumerator < KeyValuePair < TKey , TValue > > GetEnumerator ( )
{
return new Enumerator ( this ) ;
}
/// <summary>
/// Make from generation old generation.
/// </summary>
/// <remarks>
/// When generation is old, <see cref="IGeneration.TryGetValue"/> hit removes element from the generation.
/// </remarks>
/// <seealso cref="IGeneration.Clear"/>
public void MakeOld ( )
{
m_newGeneration = false ;
}
/// <summary>
/// Remove element associated with the key from the generation.
/// </summary>
/// <param name="bucketIndex">
/// The element's bucket index.
/// </param>
/// <param name="key">
/// The element's key.
/// </param>
/// <returns>
/// <see langword="true"/>, if remove was successful; otherwise <see langword="false"/>.
/// </returns>
public bool Remove ( int bucketIndex , TKey key )
{
int previousIndex ;
int entryIndex = FindElementIndex ( bucketIndex , key , false , out previousIndex ) ;
if ( entryIndex ! = - 1 )
{
RemoveElement ( bucketIndex , entryIndex , previousIndex ) ;
AccessedSinceLastTimeCheck = true ;
return true ;
}
return false ;
}
/// <summary>
/// Set or add element to generation.
/// </summary>
/// <param name="bucketIndex">
/// The element's bucket index.
/// </param>
/// <param name="key">
/// The element's key.
/// </param>
/// <param name="value">
/// The element's value.
/// </param>
/// <param name="size">
/// The element's size.
/// </param>
/// <returns>
/// <see langword="true"/>, if setting or adding was successful; otherwise <see langword="false"/>.
/// </returns>
/// <remarks>
/// <para>
/// If element was already existing in generation and new element size fits to collection limits,
/// then it's value is replaced with new one and size information is updated. If element didn't
/// exists in generation before, then generation must have empty space for a new element and
/// size must fit generation's limits, before element is added to generation.
/// </para>
/// </remarks>
public bool Set ( int bucketIndex , TKey key , TValue value , long size )
{
Debug . Assert ( m_newGeneration , "It is possible to insert new elements only to newest generation." ) ;
Debug . Assert ( size > 0 , "New element size should be more than 0." ) ;
int previousIndex ;
int elementIndex = FindElementIndex ( bucketIndex , key , true , out previousIndex ) ;
if ( elementIndex = = - 1 )
{
// New key
if ( Size + size > m_cache . m_generationMaxSize | |
( m_nextUnusedElement = = m_cache . m_generationElementCount & & m_freeCount = = 0 ) )
{
// Generation is full
return false ;
}
// Increase size of generation
Size + = size ;
// Get first free entry and update free entry list
if ( m_firstFreeElement ! = - 1 )
{
// There was entry that was removed
elementIndex = m_firstFreeElement ;
m_firstFreeElement = m_elements [ elementIndex ] . Next ;
m_freeCount - - ;
}
else
{
// No entries removed so far - just take a last one
elementIndex = m_nextUnusedElement ;
m_nextUnusedElement + + ;
}
Debug . Assert ( m_elements [ elementIndex ] . IsFree , "Allocated element is not free." ) ;
// Move new entry to front
m_elements [ elementIndex ] . Next = m_buckets [ bucketIndex ] ;
m_buckets [ bucketIndex ] = elementIndex ;
// Set key and update count
m_elements [ elementIndex ] . Key = key ;
}
else
{
// Existing key
if ( Size - m_elements [ elementIndex ] . Size + size > m_cache . m_generationMaxSize )
{
// Generation is full
// Remove existing element, because generation is going to be recycled to
// old generation and element is stored to new generation
RemoveElement ( bucketIndex , elementIndex , previousIndex ) ;
return false ;
}
// Update generation's size
Size = Size - m_elements [ elementIndex ] . Size + size ;
}
// Finally set value and size
m_elements [ elementIndex ] . Value = value ;
m_elements [ elementIndex ] . Size = size ;
// Success - key was inserterted to generation
AccessedSinceLastTimeCheck = true ;
return true ;
}
/// <summary>
/// Try to get element associated with key.
/// </summary>
/// <param name="bucketIndex">
/// The element's bucket index.
/// </param>
/// <param name="key">
/// The element's key.
/// </param>
/// <param name="value">
/// The element's value.
/// </param>
/// <param name="size">
/// The element's size.
/// </param>
/// <returns>
/// <see langword="true"/>, if element was successful retrieved; otherwise <see langword="false"/>.
/// </returns>
/// <remarks>
/// <para>
/// If element is not found from generation then <paramref name="value"/> and <paramref name="size"/>
/// are set to default value (default(TValue) and 0).
/// </para>
/// </remarks>
public bool TryGetValue ( int bucketIndex , TKey key , out TValue value , out long size )
{
// Find entry index,
int previousIndex ;
int elementIndex = FindElementIndex ( bucketIndex , key , m_newGeneration , out previousIndex ) ;
if ( elementIndex = = - 1 )
{
value = default ( TValue ) ;
size = 0 ;
return false ;
}
value = m_elements [ elementIndex ] . Value ;
size = m_elements [ elementIndex ] . Size ;
if ( ! m_newGeneration )
{
// Old generation - remove element, because it is moved to new generation
RemoveElement ( bucketIndex , elementIndex , previousIndex ) ;
}
AccessedSinceLastTimeCheck = true ;
return true ;
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
IEnumerator IEnumerable . GetEnumerator ( )
{
return GetEnumerator ( ) ;
}
# endregion
}
# endregion
#region Nested type: IGeneration
/// <summary>
/// Cache element generation interface
/// </summary>
/// <remarks>
/// <para>
/// Generation can hold limited count of elements and limited size of data.
/// </para>
/// <para>
/// There are two kind generations: "new generation" and "old generation(s)". All new elements
/// are added to "new generation".
/// </para>
/// </remarks>
protected interface IGeneration : IEnumerable < KeyValuePair < TKey , TValue > >
{
/// <summary>
/// Gets or sets a value indicating whether generation was accessed since last time check.
/// </summary>
bool AccessedSinceLastTimeCheck { get ; set ; }
/// <summary>
/// Gets element count in generation.
/// </summary>
int Count { get ; }
/// <summary>
/// Gets or sets generation's expiration time.
/// </summary>
DateTime ExpirationTime { get ; set ; }
/// <summary>
/// Gets size of data stored to generation.
/// </summary>
long Size { get ; }
/// <summary>
/// Clear all elements from the generation and make generation new again.
/// </summary>
/// <remarks>
/// When generation is new, it is allowed to add new elements to it and
/// <see cref="TryGetValue"/>doesn't remove elements from it.
/// </remarks>
/// <seealso cref="MakeOld"/>
void Clear ( ) ;
/// <summary>
/// Determines whether the <see cref="IGeneration"/> contains an element with the specific key.
/// </summary>
/// <param name="bucketIndex">
/// The bucket index for the <see cref="key"/> to locate in <see cref="IGeneration"/>.
/// </param>
/// <param name="key">
/// The key to locate in the <see cref="IGeneration"/>.
/// </param>
/// <returns>
/// <see langword="true"/>if the <see cref="IGeneration"/> contains an element with the <see cref="key"/>;
/// otherwise <see langword="false"/>.
/// </returns>
bool Contains ( int bucketIndex , TKey key ) ;
/// <summary>
/// Make from generation old generation.
/// </summary>
/// <remarks>
/// When generation is old, <see cref="TryGetValue"/> hit removes element from the generation.
/// </remarks>
/// <seealso cref="Clear"/>
void MakeOld ( ) ;
/// <summary>
/// Remove element associated with the key from the generation.
/// </summary>
/// <param name="bucketIndex">
/// The element's bucket index.
/// </param>
/// <param name="key">
/// The element's key.
/// </param>
/// <returns>
/// <see langword="true"/>, if remove was successful; otherwise <see langword="false"/>.
/// </returns>
bool Remove ( int bucketIndex , TKey key ) ;
/// <summary>
/// Set or add element to generation.
/// </summary>
/// <param name="bucketIndex">
/// The element's bucket index.
/// </param>
/// <param name="key">
/// The element's key.
/// </param>
/// <param name="value">
/// The element's value.
/// </param>
/// <param name="size">
/// The element's size.
/// </param>
/// <returns>
/// <see langword="true"/>, if setting or adding was successful; otherwise <see langword="false"/>.
/// </returns>
/// <remarks>
/// <para>
/// If element was already existing in generation and new element size fits to collection limits,
/// then it's value is replaced with new one and size information is updated. If element didn't
/// exists in generation before, then generation must have empty space for a new element and
/// size must fit generation's limits, before element is added to generation.
/// </para>
/// </remarks>
bool Set ( int bucketIndex , TKey key , TValue value , long size ) ;
/// <summary>
/// Try to get element associated with key.
/// </summary>
/// <param name="bucketIndex">
/// The element's bucket index.
/// </param>
/// <param name="key">
/// The element's key.
/// </param>
/// <param name="value">
/// The element's value.
/// </param>
/// <param name="size">
/// The element's size.
/// </param>
/// <returns>
/// <see langword="true"/>, if element was successful retrieved; otherwise <see langword="false"/>.
/// </returns>
/// <remarks>
/// <para>
/// If element is not found from generation then <paramref name="value"/> and <paramref name="size"/>
/// are set to default value (default(TValue) and 0).
/// </para>
/// </remarks>
bool TryGetValue ( int bucketIndex , TKey key , out TValue value , out long size ) ;
}
# endregion
#region ICnmCache<TKey,TValue> Members
/// <summary>
/// Gets current count of elements stored to <see cref="ICnmCache{TKey,TValue}"/>.
/// </summary>
/// <remarks>
/// <para>
/// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count,
/// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element.
/// </para>
/// </remarks>
/// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/>
public int Count
{
get { return m_newGeneration . Count + m_oldGeneration . Count ; }
}
/// <summary>
/// Gets or sets elements expiration time.
/// </summary>
/// <value>
/// Elements expiration time.
/// </value>
/// <remarks>
/// <para>
/// When element has been stored in <see cref="ICnmCache{TKey,TValue}"/> longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>
/// and it is not accessed through <see cref="ICnmCache{TKey,TValue}.TryGetValue"/> method or element's value is
/// not replaced by <see cref="ICnmCache{TKey,TValue}.Set"/> method, then it is automatically removed from the
/// <see cref="ICnmCache{TKey,TValue}"/>.
/// </para>
/// <para>
/// It is possible that <see cref="ICnmCache{TKey,TValue}"/> implementation removes element before it's expiration time,
/// because total size or count of elements stored to cache is larger than <see cref="ICnmCache{TKey,TValue}.MaxSize"/> or <see cref="ICnmCache{TKey,TValue}.MaxCount"/>.
/// </para>
/// <para>
/// It is also possible that element stays in cache longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>.
/// </para>
/// <para>
/// Calling <see cref="ICnmCache{TKey,TValue}.PurgeExpired"/> try to remove all elements that are expired.
/// </para>
/// <para>
/// To disable time limit in cache, set <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> to <see cref="DateTime.MaxValue"/>.
/// </para>
/// </remarks>
/// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Count"/>
/// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/>
/// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Size"/>
public TimeSpan ExpirationTime
{
get { return m_expirationTime ; }
set
{
if ( value < MinExpirationTime )
value = MinExpirationTime ;
if ( m_expirationTime = = value )
return ;
m_newGeneration . ExpirationTime = ( m_newGeneration . ExpirationTime - m_expirationTime ) + value ;
m_oldGeneration . ExpirationTime = ( m_oldGeneration . ExpirationTime - m_expirationTime ) + value ;
m_expirationTime = value ;
PurgeExpired ( ) ;
}
}
/// <summary>
/// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting count of elements.
/// </summary>
/// <value>
/// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> count of elements is limited;
/// otherwise, <see langword="false"/>.
/// </value>
/// <remarks>
/// <para>
/// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count,
/// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element.
/// </para>
/// </remarks>
/// <seealso cref="ICnmCache{TKey,TValue}.Count"/>
/// <seealso cref="ICnmCache{TKey,TValue}.MaxCount"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/>
public bool IsCountLimited
{
get { return true ; }
}
/// <summary>
/// Gets a value indicating whether <see cref="ICnmCache{TKey,TValue}"/> is limiting size of elements.
/// </summary>
/// <value>
/// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> total size of elements is limited;
/// otherwise, <see langword="false"/>.
/// </value>
/// <remarks>
/// <para>
/// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements,
/// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element.
/// </para>
/// </remarks>
/// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Size"/>
/// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/>
public bool IsSizeLimited
{
get { return true ; }
}
/// <summary>
/// Gets a value indicating whether or not access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe).
/// </summary>
/// <value>
/// <see langword="true"/> if access to the <see cref="ICnmCache{TKey,TValue}"/> is synchronized (thread safe);
/// otherwise, <see langword="false"/>.
/// </value>
/// <remarks>
/// <para>
/// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/> object, use
/// <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> in <see cref="CnmSynchronizedCache{TKey,TValue}"/> class
/// to retrieve synchronized wrapper for <see cref="ICnmCache{TKey,TValue}"/> object.
/// </para>
/// </remarks>
/// <seealso cref="ICnmCache{TKey,TValue}.SyncRoot"/>
/// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/>
public bool IsSynchronized
{
get { return false ; }
}
/// <summary>
/// Gets a value indicating whether elements stored to <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time.
/// </summary>
/// <value>
/// <see langword="true"/> if the <see cref="ICnmCache{TKey,TValue}"/> has a fixed total size of elements;
/// otherwise, <see langword="false"/>.
/// </value>
/// <remarks>
/// If <see cref="ICnmCache{TKey,TValue}"/> have limited inactivity time and element is not accessed through <see cref="ICnmCache{TKey,TValue}.Set"/>
/// or <see cref="ICnmCache{TKey,TValue}.TryGetValue"/> methods in <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> , then element is automatically removed from
/// the cache. Depending on implementation of the <see cref="ICnmCache{TKey,TValue}"/>, some of the elements may
/// stay longer in cache.
/// </remarks>
/// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/>
/// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
public bool IsTimeLimited
{
get { return ExpirationTime ! = TimeSpan . MaxValue ; }
}
/// <summary>
/// Gets or sets maximal allowed count of elements that can be stored to <see cref="ICnmCache{TKey,TValue}"/>.
/// </summary>
/// <value>
/// <see cref="int.MaxValue"/>, if <see cref="ICnmCache{TKey,TValue}"/> is not limited by count of elements;
/// otherwise maximal allowed count of elements.
/// </value>
/// <remarks>
/// <para>
/// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count,
/// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element.
/// </para>
/// </remarks>
public int MaxCount
{
get { return m_maxCount ; }
set
{
if ( value < 8 )
value = 8 ;
if ( m_maxCount = = value )
return ;
m_maxCount = value ;
Initialize ( ) ;
}
}
/// <summary>
/// <para>Gets maximal allowed element size.</para>
/// </summary>
/// <value>
/// Maximal allowed element size.
/// </value>
/// <remarks>
/// <para>
/// If element's size is larger than <see cref="ICnmCache{TKey,TValue}.MaxElementSize"/>, then element is
/// not added to the <see cref="ICnmCache{TKey,TValue}"/>.
/// </para>
/// </remarks>
/// <seealso cref="ICnmCache{TKey,TValue}.Set"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Size"/>
/// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/>
public long MaxElementSize
{
get { return m_maxElementSize ; }
private set { m_maxElementSize = value ; }
}
/// <summary>
/// Gets or sets maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>.
/// </summary>
/// <value>
/// Maximal allowed total size for elements stored to <see cref="ICnmCache{TKey,TValue}"/>.
/// </value>
/// <remarks>
/// <para>
/// Normally size is total bytes used by elements in the cache. But it can be any other suitable unit of measure.
/// </para>
/// <para>
/// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements,
/// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element.
/// </para>
/// </remarks>
/// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Size"/>
public long MaxSize
{
get { return m_maxSize ; }
set
{
if ( value < 8 )
value = 8 ;
if ( m_maxSize = = value )
return ;
m_maxSize = value ;
Initialize ( ) ;
}
}
/// <summary>
/// Gets total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>.
/// </summary>
/// <value>
/// Total size of elements stored to <see cref="ICnmCache{TKey,TValue}"/>.
/// </value>
/// <remarks>
/// <para>
/// Normally bytes, but can be any suitable unit of measure.
/// </para>
/// <para>
/// Element's size is given when element is added or replaced by <see cref="ICnmCache{TKey,TValue}.Set"/> method.
/// </para>
/// <para>
/// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements,
/// <see cref="ICnmCache{TKey,TValue}"/> will remove less recently used elements until it can fit an new element.
/// </para>
/// </remarks>
/// <seealso cref="ICnmCache{TKey,TValue}.MaxElementSize"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.MaxSize"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/>
public long Size
{
get { return m_newGeneration . Size + m_oldGeneration . Size ; }
}
/// <summary>
/// Gets an object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>.
/// </summary>
/// <value>
/// An object that can be used to synchronize access to the <see cref="ICnmCache{TKey,TValue}"/>.
/// </value>
/// <remarks>
/// <para>
/// To get synchronized (thread safe) access to <see cref="ICnmCache{TKey,TValue}"/>, use <see cref="CnmSynchronizedCache{TKey,TValue}"/>
/// method <see cref="CnmSynchronizedCache{TKey,TValue}.Synchronized"/> to retrieve synchronized wrapper interface to
/// <see cref="ICnmCache{TKey,TValue}"/>.
/// </para>
/// </remarks>
/// <seealso cref="ICnmCache{TKey,TValue}.IsSynchronized"/>
/// <seealso cref="CnmSynchronizedCache{TKey,TValue}"/>
public object SyncRoot
{
get { return m_syncRoot ; }
}
/// <summary>
/// Removes all elements from the <see cref="ICnmCache{TKey,TValue}"/>.
/// </summary>
/// <seealso cref="ICnmCache{TKey,TValue}.Set"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Remove"/>
/// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/>
/// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/>
/// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
public void Clear ( )
{
m_newGeneration . Clear ( ) ;
m_oldGeneration . Clear ( ) ;
m_oldGeneration . MakeOld ( ) ;
m_version + + ;
}
/// <summary>
/// Returns an enumerator that iterates through the elements stored to <see cref="CnmMemoryCache{TKey,TValue}"/>.
/// </summary>
/// <returns>
/// A <see cref="IEnumerator{T}"/> that can be used to iterate through the collection.
/// </returns>
/// <filterpriority>1</filterpriority>
public IEnumerator < KeyValuePair < TKey , TValue > > GetEnumerator ( )
{
return new Enumerator ( this ) ;
}
/// <summary>
/// Purge expired elements from the <see cref="ICnmCache{TKey,TValue}"/>.
/// </summary>
/// <remarks>
/// <para>
/// Element becomes expired when last access time to it has been longer time than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/>.
/// </para>
/// <para>
/// Depending on <see cref="ICnmCache{TKey,TValue}"/> implementation, some of expired elements
/// may stay longer than <see cref="ICnmCache{TKey,TValue}.ExpirationTime"/> in the cache.
/// </para>
/// </remarks>
/// <seealso cref="ICnmCache{TKey,TValue}.IsTimeLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.ExpirationTime"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Set"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Remove"/>
/// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/>
/// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Clear"/>
public void PurgeExpired ( )
{
m_operationsBetweenTimeChecks = DefaultOperationsBetweenTimeChecks ;
if ( ! IsTimeLimited )
return ;
DateTime now = DateTime . Now ;
if ( m_newGeneration . AccessedSinceLastTimeCheck )
{
// New generation has been accessed since last check
// Update it's expiration time.
m_newGeneration . ExpirationTime = now + ExpirationTime ;
m_newGeneration . AccessedSinceLastTimeCheck = false ;
}
else if ( m_newGeneration . ExpirationTime < now )
{
// New generation has been expired.
// --> also old generation must be expired.
PurgeGeneration ( m_newGeneration ) ;
PurgeGeneration ( m_oldGeneration ) ;
return ;
}
if ( m_oldGeneration . ExpirationTime < now )
PurgeGeneration ( m_oldGeneration ) ;
}
/// <summary>
/// Removes element associated with <paramref name="key"/> from the <see cref="ICnmCache{TKey,TValue}"/>.
/// </summary>
/// <param name="key">
/// The key that is associated with element to remove from the <see cref="ICnmCache{TKey,TValue}"/>.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="key"/>is <see langword="null"/>.
/// </exception>
/// <seealso cref="ICnmCache{TKey,TValue}.Set"/>
/// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/>
/// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Clear"/>
/// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
public void Remove ( TKey key )
{
if ( key = = null )
throw new ArgumentNullException ( "key" ) ;
int bucketIndex = GetBucketIndex ( key ) ;
if ( ! m_newGeneration . Remove ( bucketIndex , key ) )
{
if ( ! m_oldGeneration . Remove ( bucketIndex , key ) )
{
CheckExpired ( ) ;
return ;
}
}
CheckExpired ( ) ;
m_version + + ;
}
/// <summary>
/// Removes elements that are associated with one of <paramref name="keys"/> from the <see cref="ICnmCache{TKey,TValue}"/>.
/// </summary>
/// <param name="keys">
/// The keys that are associated with elements to remove from the <see cref="ICnmCache{TKey,TValue}"/>.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="keys"/>is <see langword="null"/>.
/// </exception>
/// <seealso cref="ICnmCache{TKey,TValue}.Set"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Remove"/>
/// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Clear"/>
/// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
public void RemoveRange ( IEnumerable < TKey > keys )
{
if ( keys = = null )
throw new ArgumentNullException ( "keys" ) ;
foreach ( TKey key in keys )
{
if ( key = = null )
continue ;
int bucketIndex = GetBucketIndex ( key ) ;
if ( ! m_newGeneration . Remove ( bucketIndex , key ) )
m_oldGeneration . Remove ( bucketIndex , key ) ;
}
CheckExpired ( ) ;
m_version + + ;
}
/// <summary>
/// Add or replace an element with the provided <paramref name="key"/>, <paramref name="value"/> and <paramref name="size"/> to
/// <see cref="ICnmCache{TKey,TValue}"/>.
/// </summary>
/// <param name="key">
/// The object used as the key of the element. Can't be <see langword="null"/> reference.
/// </param>
/// <param name="value">
/// The object used as the value of the element to add or replace. <see langword="null"/> is allowed.
/// </param>
/// <param name="size">
/// The element's size. Normally bytes, but can be any suitable unit of measure.
/// </param>
/// <returns>
/// <see langword="true"/>if element has been added successfully to the <see cref="ICnmCache{TKey,TValue}"/>;
/// otherwise <see langword="false"/>.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="key"/>is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// The element's <paramref name="size"/> is less than 0.
/// </exception>
/// <remarks>
/// <para>
/// If element's <paramref name="size"/> is larger than <see cref="ICnmCache{TKey,TValue}.MaxElementSize"/>, then element is
/// not added to the <see cref="ICnmCache{TKey,TValue}"/>, however - possible older element is
/// removed from the <see cref="ICnmCache{TKey,TValue}"/>.
/// </para>
/// <para>
/// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting total size of elements,
/// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element.
/// </para>
/// <para>
/// When adding an new element to <see cref="ICnmCache{TKey,TValue}"/> that is limiting element count,
/// <see cref="ICnmCache{TKey,TValue}"/>will remove less recently used elements until it can fit an new element.
/// </para>
/// </remarks>
/// <seealso cref="ICnmCache{TKey,TValue}.IsSizeLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.IsCountLimited"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Remove"/>
/// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/>
/// <seealso cref="ICnmCache{TKey,TValue}.TryGetValue"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Clear"/>
/// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
public bool Set ( TKey key , TValue value , long size )
{
if ( key = = null )
throw new ArgumentNullException ( "key" ) ;
if ( size < 0 )
throw new ArgumentOutOfRangeException ( "size" , size , "Value's size can't be less than 0." ) ;
if ( size > MaxElementSize )
{
// Entry size is too big to fit cache - ignore it
Remove ( key ) ;
return false ;
}
if ( size = = 0 )
size = 1 ;
int bucketIndex = GetBucketIndex ( key ) ;
m_oldGeneration . Remove ( bucketIndex , key ) ;
AddToNewGeneration ( bucketIndex , key , value , size ) ;
CheckExpired ( ) ;
return true ;
}
/// <summary>
/// Gets the <paramref name="value"/> associated with the specified <paramref name="key"/>.
/// </summary>
/// <returns>
/// <see langword="true"/>if the <see cref="ICnmCache{TKey,TValue}"/> contains an element with
/// the specified key; otherwise, <see langword="false"/>.
/// </returns>
/// <param name="key">
/// The key whose <paramref name="value"/> to get.
/// </param>
/// <param name="value">
/// When this method returns, the value associated with the specified <paramref name="key"/>,
/// if the <paramref name="key"/> is found; otherwise, the
/// default value for the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="key"/>is <see langword="null"/>.
/// </exception>
/// <seealso cref="ICnmCache{TKey,TValue}.Set"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Remove"/>
/// <seealso cref="ICnmCache{TKey,TValue}.RemoveRange"/>
/// <seealso cref="ICnmCache{TKey,TValue}.Clear"/>
/// <seealso cref="ICnmCache{TKey,TValue}.PurgeExpired"/>
public bool TryGetValue ( TKey key , out TValue value )
{
if ( key = = null )
throw new ArgumentNullException ( "key" ) ;
int bucketIndex = GetBucketIndex ( key ) ;
long size ;
if ( m_newGeneration . TryGetValue ( bucketIndex , key , out value , out size ) )
{
CheckExpired ( ) ;
return true ;
}
if ( m_oldGeneration . TryGetValue ( bucketIndex , key , out value , out size ) )
{
// Move element to new generation
AddToNewGeneration ( bucketIndex , key , value , size ) ;
CheckExpired ( ) ;
return true ;
}
CheckExpired ( ) ;
return false ;
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
/// <filterpriority>2</filterpriority>
IEnumerator IEnumerable . GetEnumerator ( )
{
return GetEnumerator ( ) ;
}
# endregion
}
}