/* * 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 { /// /// Cenome memory based cache to store key/value pairs (elements) limited time and/or limited size. /// /// /// The type of keys in the cache. /// /// /// The type of values in the dictionary. /// /// /// /// 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 method, they are moved to newest generation. /// /// public class CnmMemoryCache : ICnmCache { /// /// Default maximal count. /// /// public const int DefaultMaxCount = 4096; /// /// Default maximal size. /// /// /// /// 128MB = 128 * 1024^2 = 134 217 728 bytes. /// /// /// public const long DefaultMaxSize = 134217728; /// /// How many operations between time checks. /// private const int DefaultOperationsBetweenTimeChecks = 40; /// /// Default expiration time. /// /// /// /// 30 minutes. /// /// public static readonly TimeSpan DefaultExpirationTime = TimeSpan.FromMinutes(30.0); /// /// Minimal allowed expiration time. /// /// /// /// 5 minutes. /// /// public static readonly TimeSpan MinExpirationTime = TimeSpan.FromSeconds(10.0); /// /// Comparer used to compare element keys. /// /// /// Comparer is initialized by constructor. /// /// public readonly IEqualityComparer Comparer; /// /// Expiration time. /// private TimeSpan m_expirationTime = DefaultExpirationTime; /// /// Generation bucket count. /// private int m_generationBucketCount; /// /// Generation entry count. /// private int m_generationElementCount; /// /// Generation max size. /// private long m_generationMaxSize; /// /// Maximal allowed count of elements. /// private int m_maxCount; /// /// Maximal allowed total size of elements. /// private long m_maxElementSize; /// /// Maximal size. /// private long m_maxSize; /// /// New generation. /// private IGeneration m_newGeneration; /// /// Old generation. /// private IGeneration m_oldGeneration; /// /// Operations between time check. /// private int m_operationsBetweenTimeChecks = DefaultOperationsBetweenTimeChecks; /// /// Synchronization root object, should always be private and exists always /// private readonly object m_syncRoot = new object(); /// /// Version of cache. /// /// /// /// Updated every time when cache has been changed (element removed, expired, added, replaced). /// /// private int m_version; /// /// Initializes a new instance of the class. /// public CnmMemoryCache() : this(DefaultMaxSize) { } /// /// Initializes a new instance of the class. /// /// /// Maximal cache size. /// public CnmMemoryCache(long maximalSize) : this(maximalSize, DefaultMaxCount) { } /// /// Initializes a new instance of the class. /// /// /// Maximal cache size. /// /// /// Maximal element count. /// public CnmMemoryCache(long maximalSize, int maximalCount) : this(maximalSize, maximalCount, DefaultExpirationTime) { } /// /// Initializes a new instance of the class. /// /// /// Maximal cache size. /// /// /// Maximal element count. /// /// /// Elements expiration time. /// public CnmMemoryCache(long maximalSize, int maximalCount, TimeSpan expirationTime) : this(maximalSize, maximalCount, expirationTime, EqualityComparer.Default) { } /// /// Initializes a new instance of the class. /// /// /// Maximal cache size. /// /// /// Maximal element count. /// /// /// Elements expiration time. /// /// /// Comparer used for comparing elements. /// /// /// is reference. /// public CnmMemoryCache(long maximalSize, int maximalCount, TimeSpan expirationTime, IEqualityComparer 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(); } /// /// Add element to new generation. /// /// /// The bucket index. /// /// /// The element's key. /// /// /// The element's value. /// /// /// The element's size. /// 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++; } /// /// /// Get keys bucket index. /// /// /// /// /// Key which bucket index is being retrieved. /// /// /// /// /// Bucket index. /// /// /// /// /// Method uses to calculate hash code. /// /// /// 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. /// /// protected virtual int GetBucketIndex(TKey key) { return (Comparer.GetHashCode(key) & 0x7FFFFFFF) % m_generationBucketCount; } /// /// Purge generation from the cache. /// /// /// The generation that is purged. /// protected virtual void PurgeGeneration(IGeneration generation) { generation.Clear(); m_version++; } /// /// check expired. /// 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(); } /// /// Initialize cache. /// 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(); } /// /// Recycle generations. /// 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 /// /// Key and value pair enumerator. /// private class Enumerator : IEnumerator> { /// /// Current enumerator. /// private int m_currentEnumerator = -1; /// /// Enumerators to different generations. /// private readonly IEnumerator>[] m_generationEnumerators = new IEnumerator>[2]; /// /// Initializes a new instance of the class. /// /// /// The cache. /// public Enumerator(CnmMemoryCache cache) { m_generationEnumerators[ 0 ] = cache.m_newGeneration.GetEnumerator(); m_generationEnumerators[ 1 ] = cache.m_oldGeneration.GetEnumerator(); } #region IEnumerator> Members /// /// Gets the element in the collection at the current position of the enumerator. /// /// /// The element in the collection at the current position of the enumerator. /// /// /// The enumerator has reach end of collection or is not called. /// public KeyValuePair Current { get { if (m_currentEnumerator == -1 || m_currentEnumerator >= m_generationEnumerators.Length) throw new InvalidOperationException(); return m_generationEnumerators[ m_currentEnumerator ].Current; } } /// /// Gets the current element in the collection. /// /// /// The current element in the collection. /// /// /// The enumerator is positioned before the first element of the collection or after the last element. /// 2 object IEnumerator.Current { get { return Current; } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// 2 public void Dispose() { } /// /// Advances the enumerator to the next element of the collection. /// /// /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. /// /// /// The collection was modified after the enumerator was created. /// /// 2 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; } /// /// Sets the enumerator to its initial position, which is before the first element in the collection. /// /// /// The collection was modified after the enumerator was created. /// /// 2 public void Reset() { foreach (IEnumerator> enumerator in m_generationEnumerators) { enumerator.Reset(); } m_currentEnumerator = -1; } #endregion } #endregion #region Nested type: HashGeneration /// /// Hash generation class /// /// /// /// Current implementation is based to separated chaining with move-to-front heuristics. Hash generations have fixed /// amount of buckets and it is never rehashed. /// /// /// Read more about hash tables from Wiki article. /// /// /// private class HashGeneration : IGeneration { /// /// Value indicating whether generation was accessed since last time check. /// private bool m_accessedSinceLastTimeCheck; /// /// Index of first element's in element chain. /// /// /// -1 if there is no element in bucket; otherwise first element's index in the element chain. /// /// /// 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. /// private readonly int[] m_buckets; /// /// Cache object. /// private readonly CnmMemoryCache m_cache; /// /// Generation's element array. /// /// private readonly Element[] m_elements; /// /// Generation's expiration time. /// private DateTime m_expirationTime1; /// /// Index to first free element. /// private int m_firstFreeElement; /// /// Free element count. /// /// /// 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. /// private int m_freeCount; /// /// Is this generation "new generation". /// private bool m_newGeneration; /// /// Next unused entry. /// private int m_nextUnusedElement; /// /// Size of data stored to generation. /// private long m_size; /// /// Initializes a new instance of the class. /// /// /// The cache. /// public HashGeneration(CnmMemoryCache cache) { m_cache = cache; m_elements = new Element[m_cache.m_generationElementCount]; m_buckets = new int[m_cache.m_generationBucketCount]; Clear(); } /// /// Find element's index /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// Move element to front of elements. /// /// /// The previous element's index. /// /// /// Element's index, if found from the generation; -1 otherwise (if element is not found the generation). /// 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; } /// /// Remove element front the generation. /// /// /// The bucket index. /// /// /// The element index. /// /// /// The element's previous index. /// 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 /// /// Element that stores key, next element in chain, size and value. /// private struct Element { /// /// Element's key. /// public TKey Key; /// /// Next element in chain. /// /// /// 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. /// public int Next; /// /// Size of element. /// /// /// 0 if element is free; otherwise larger than 0. /// public long Size; /// /// Element's value. /// /// /// It is possible that this value is even when element /// have value - element's value is then reference. /// public TValue Value; /// /// Gets a value indicating whether element is free or have value. /// /// /// when element is free; otherwise . /// public bool IsFree { get { return Size == 0; } } } #endregion #region Nested type: Enumerator /// /// Key value pair enumerator for object. /// private class Enumerator : IEnumerator> { /// /// Current element. /// private KeyValuePair m_current; /// /// Current index. /// private int m_currentIndex; /// /// Generation that is being enumerated. /// private readonly HashGeneration m_generation; /// /// Cache version. /// /// /// When cache is change, version number is changed. /// /// private readonly int m_version; /// /// Initializes a new instance of the class. /// /// /// The generation. /// public Enumerator(HashGeneration generation) { m_generation = generation; m_version = m_generation.m_cache.m_version; } #region IEnumerator> Members /// /// Gets the element in the collection at the current position of the enumerator. /// /// /// The element in the collection at the current position of the enumerator. /// /// /// The enumerator has reach end of collection or is not called. /// public KeyValuePair Current { get { if (m_currentIndex == 0 || m_currentIndex >= m_generation.Count) throw new InvalidOperationException(); return m_current; } } /// /// Gets the current element in the collection. /// /// /// The current element in the collection. /// /// /// The enumerator is positioned before the first element of the collection or after the last element. /// 2 object IEnumerator.Current { get { return Current; } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// /// 2 public void Dispose() { } /// /// Advances the enumerator to the next element of the collection. /// /// /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. /// /// /// The collection was modified after the enumerator was created. /// 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(m_generation.m_elements[ m_currentIndex ].Key, m_generation.m_elements[ m_currentIndex ].Value); m_currentIndex++; return true; } m_current = new KeyValuePair(); return false; } /// /// Sets the enumerator to its initial position, which is before the first element in the collection. /// /// /// The collection was modified after the enumerator was created. /// /// 2 public void Reset() { if (m_version != m_generation.m_cache.m_version) throw new InvalidOperationException(); m_currentIndex = 0; } #endregion } #endregion #region IGeneration Members /// /// Gets or sets a value indicating whether generation was accessed since last time check. /// public bool AccessedSinceLastTimeCheck { get { return m_accessedSinceLastTimeCheck; } set { m_accessedSinceLastTimeCheck = value; } } /// /// Gets element count in generation. /// public int Count { get { return m_nextUnusedElement - m_freeCount; } } /// /// Gets or sets generation's expiration time. /// public DateTime ExpirationTime { get { return m_expirationTime1; } set { m_expirationTime1 = value; } } /// /// Gets or sets size of data stored to generation. /// public long Size { get { return m_size; } private set { m_size = value; } } /// /// Clear all elements from the generation and make generation new again. /// /// /// When generation is new, it is allowed to add new elements to it and /// doesn't remove elements from it. /// /// 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; } /// /// Determines whether the contains an element with the specific key. /// /// /// The bucket index for the to locate in . /// /// /// The key to locate in the . /// /// /// if the contains an element with the ; /// otherwise . /// public bool Contains(int bucketIndex, TKey key) { int previousIndex; if (FindElementIndex(bucketIndex, key, true, out previousIndex) == -1) return false; AccessedSinceLastTimeCheck = true; return true; } /// /// Returns an enumerator that iterates through the elements stored . /// /// /// A that can be used to iterate through the . /// /// 1 public IEnumerator> GetEnumerator() { return new Enumerator(this); } /// /// Make from generation old generation. /// /// /// When generation is old, hit removes element from the generation. /// /// public void MakeOld() { m_newGeneration = false; } /// /// Remove element associated with the key from the generation. /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// , if remove was successful; otherwise . /// 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; } /// /// Set or add element to generation. /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// The element's value. /// /// /// The element's size. /// /// /// , if setting or adding was successful; otherwise . /// /// /// /// 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. /// /// 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; } /// /// Try to get element associated with key. /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// The element's value. /// /// /// The element's size. /// /// /// , if element was successful retrieved; otherwise . /// /// /// /// If element is not found from generation then and /// are set to default value (default(TValue) and 0). /// /// 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; } /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate through the collection. /// /// 2 IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } #endregion #region Nested type: IGeneration /// /// Cache element generation interface /// /// /// /// Generation can hold limited count of elements and limited size of data. /// /// /// There are two kind generations: "new generation" and "old generation(s)". All new elements /// are added to "new generation". /// /// protected interface IGeneration : IEnumerable> { /// /// Gets or sets a value indicating whether generation was accessed since last time check. /// bool AccessedSinceLastTimeCheck { get; set; } /// /// Gets element count in generation. /// int Count { get; } /// /// Gets or sets generation's expiration time. /// DateTime ExpirationTime { get; set; } /// /// Gets size of data stored to generation. /// long Size { get; } /// /// Clear all elements from the generation and make generation new again. /// /// /// When generation is new, it is allowed to add new elements to it and /// doesn't remove elements from it. /// /// void Clear(); /// /// Determines whether the contains an element with the specific key. /// /// /// The bucket index for the to locate in . /// /// /// The key to locate in the . /// /// /// if the contains an element with the ; /// otherwise . /// bool Contains(int bucketIndex, TKey key); /// /// Make from generation old generation. /// /// /// When generation is old, hit removes element from the generation. /// /// void MakeOld(); /// /// Remove element associated with the key from the generation. /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// , if remove was successful; otherwise . /// bool Remove(int bucketIndex, TKey key); /// /// Set or add element to generation. /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// The element's value. /// /// /// The element's size. /// /// /// , if setting or adding was successful; otherwise . /// /// /// /// 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. /// /// bool Set(int bucketIndex, TKey key, TValue value, long size); /// /// Try to get element associated with key. /// /// /// The element's bucket index. /// /// /// The element's key. /// /// /// The element's value. /// /// /// The element's size. /// /// /// , if element was successful retrieved; otherwise . /// /// /// /// If element is not found from generation then and /// are set to default value (default(TValue) and 0). /// /// bool TryGetValue(int bucketIndex, TKey key, out TValue value, out long size); } #endregion #region ICnmCache Members /// /// Gets current count of elements stored to . /// /// /// /// When adding an new element to that is limiting element count, /// will remove less recently used elements until it can fit an new element. /// /// /// /// /// /// public int Count { get { return m_newGeneration.Count + m_oldGeneration.Count; } } /// /// Gets or sets elements expiration time. /// /// /// Elements expiration time. /// /// /// /// When element has been stored in longer than /// and it is not accessed through method or element's value is /// not replaced by method, then it is automatically removed from the /// . /// /// /// It is possible that implementation removes element before it's expiration time, /// because total size or count of elements stored to cache is larger than or . /// /// /// It is also possible that element stays in cache longer than . /// /// /// Calling try to remove all elements that are expired. /// /// /// To disable time limit in cache, set to . /// /// /// /// /// /// /// /// /// /// 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(); } } /// /// Gets a value indicating whether is limiting count of elements. /// /// /// if the count of elements is limited; /// otherwise, . /// /// /// /// When adding an new element to that is limiting element count, /// will remove less recently used elements until it can fit an new element. /// /// /// /// /// /// public bool IsCountLimited { get { return true; } } /// /// Gets a value indicating whether is limiting size of elements. /// /// /// if the total size of elements is limited; /// otherwise, . /// /// /// /// When adding an new element to that is limiting total size of elements, /// will remove less recently used elements until it can fit an new element. /// /// /// /// /// /// /// public bool IsSizeLimited { get { return true; } } /// /// Gets a value indicating whether or not access to the is synchronized (thread safe). /// /// /// if access to the is synchronized (thread safe); /// otherwise, . /// /// /// /// To get synchronized (thread safe) access to object, use /// in class /// to retrieve synchronized wrapper for object. /// /// /// /// public bool IsSynchronized { get { return false; } } /// /// Gets a value indicating whether elements stored to have limited inactivity time. /// /// /// if the has a fixed total size of elements; /// otherwise, . /// /// /// If have limited inactivity time and element is not accessed through /// or methods in , then element is automatically removed from /// the cache. Depending on implementation of the , some of the elements may /// stay longer in cache. /// /// /// /// /// public bool IsTimeLimited { get { return ExpirationTime != TimeSpan.MaxValue; } } /// /// Gets or sets maximal allowed count of elements that can be stored to . /// /// /// , if is not limited by count of elements; /// otherwise maximal allowed count of elements. /// /// /// /// When adding an new element to that is limiting element count, /// will remove less recently used elements until it can fit an new element. /// /// public int MaxCount { get { return m_maxCount; } set { if (value < 8) value = 8; if (m_maxCount == value) return; m_maxCount = value; Initialize(); } } /// /// Gets maximal allowed element size. /// /// /// Maximal allowed element size. /// /// /// /// If element's size is larger than , then element is /// not added to the . /// /// /// /// /// /// public long MaxElementSize { get { return m_maxElementSize; } private set { m_maxElementSize = value; } } /// /// Gets or sets maximal allowed total size for elements stored to . /// /// /// Maximal allowed total size for elements stored to . /// /// /// /// Normally size is total bytes used by elements in the cache. But it can be any other suitable unit of measure. /// /// /// When adding an new element to that is limiting total size of elements, /// will remove less recently used elements until it can fit an new element. /// /// /// /// /// public long MaxSize { get { return m_maxSize; } set { if (value < 8) value = 8; if (m_maxSize == value) return; m_maxSize = value; Initialize(); } } /// /// Gets total size of elements stored to . /// /// /// Total size of elements stored to . /// /// /// /// Normally bytes, but can be any suitable unit of measure. /// /// /// Element's size is given when element is added or replaced by method. /// /// /// When adding an new element to that is limiting total size of elements, /// will remove less recently used elements until it can fit an new element. /// /// /// /// /// /// /// public long Size { get { return m_newGeneration.Size + m_oldGeneration.Size; } } /// /// Gets an object that can be used to synchronize access to the . /// /// /// An object that can be used to synchronize access to the . /// /// /// /// To get synchronized (thread safe) access to , use /// method to retrieve synchronized wrapper interface to /// . /// /// /// /// public object SyncRoot { get { return m_syncRoot; } } /// /// Removes all elements from the . /// /// /// /// /// /// public void Clear() { m_newGeneration.Clear(); m_oldGeneration.Clear(); m_oldGeneration.MakeOld(); m_version++; } /// /// Returns an enumerator that iterates through the elements stored to . /// /// /// A that can be used to iterate through the collection. /// /// 1 public IEnumerator> GetEnumerator() { return new Enumerator(this); } /// /// Purge expired elements from the . /// /// /// /// Element becomes expired when last access time to it has been longer time than . /// /// /// Depending on implementation, some of expired elements /// may stay longer than in the cache. /// /// /// /// /// /// /// /// /// 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); } /// /// Removes element associated with from the . /// /// /// The key that is associated with element to remove from the . /// /// /// is . /// /// /// /// /// /// 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++; } /// /// Removes elements that are associated with one of from the . /// /// /// The keys that are associated with elements to remove from the . /// /// /// is . /// /// /// /// /// /// public void RemoveRange(IEnumerable 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++; } /// /// Add or replace an element with the provided , and to /// . /// /// /// The object used as the key of the element. Can't be reference. /// /// /// The object used as the value of the element to add or replace. is allowed. /// /// /// The element's size. Normally bytes, but can be any suitable unit of measure. /// /// /// if element has been added successfully to the ; /// otherwise . /// /// /// is . /// /// /// The element's is less than 0. /// /// /// /// If element's is larger than , then element is /// not added to the , however - possible older element is /// removed from the . /// /// /// When adding an new element to that is limiting total size of elements, /// will remove less recently used elements until it can fit an new element. /// /// /// When adding an new element to that is limiting element count, /// will remove less recently used elements until it can fit an new element. /// /// /// /// /// /// /// /// /// 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; } /// /// Gets the associated with the specified . /// /// /// if the contains an element with /// the specified key; otherwise, . /// /// /// The key whose to get. /// /// /// When this method returns, the value associated with the specified , /// if the is found; otherwise, the /// default value for the type of the parameter. This parameter is passed uninitialized. /// /// /// is . /// /// /// /// /// /// 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; } /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate through the collection. /// /// 2 IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } }