/* * 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. */ 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 } }