Skip to content

Instantly share code, notes, and snippets.

@btshft
Last active May 9, 2017 20:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save btshft/06fe4be53bc588df29c7dd97e787fbfd to your computer and use it in GitHub Desktop.
Save btshft/06fe4be53bc588df29c7dd97e787fbfd to your computer and use it in GitHub Desktop.
Simple cache manager for .Net
using System;
namespace Cache.Contracts
{
/// <summary>
/// Менеджер для кеширования.
/// </summary>
public interface ICacheManager
{
/// <summary>
/// Получение значения из кеша по ключу или
/// сохранение объекта к кеш из фабрики.
/// </summary>
/// <typeparam name="T">Тип данных.</typeparam>
/// <param name="key">Ключ данных.</param>
/// <param name="factory">Фабрика значений.</param>
/// <param name="expiration">Длительность хранения.</param>
/// <returns>Данные из кеша.</returns>
T GetOrAdd<T>(string key, Func<T> factory, TimeSpan expiration) where T : class;
/// <summary>
/// Получение значения из кеша по ключу или
/// сохранение объекта к кеш из фабрики.
/// </summary>
/// <typeparam name="T">Тип данных.</typeparam>
/// <param name="key">Ключ данных.</param>
/// <param name="factory">Фабрика значений.</param>
/// <returns>Данные из кеша.</returns>
T GetOrAdd<T>(string key, Func<T> factory) where T: class;
/// <summary>
/// Добавление значения в кеш.
/// </summary>
/// <typeparam name="T">Тип данных.</typeparam>
/// <param name="key">Ключ данных.</param>
/// <param name="value">Объект для хранения.</param>
/// <param name="expiration">Длительность хранения.</param>
void Add<T>(string key, T value, TimeSpan expiration) where T: class;
/// <summary>
/// Добавление значения в кеш.
/// </summary>
/// <typeparam name="T">Тип данных.</typeparam>
/// <param name="key">Ключ данных.</param>
/// <param name="value">Объект для хранения.</param>
void Add<T>(string key, T value) where T : class;
/// <summary>
/// Добавление значения в кеш.
/// </summary>
/// <typeparam name="T">Тип данных.</typeparam>
/// <param name="key">Ключ данных.</param>
/// <param name="updater">Функтор для обновления значений.</param>
/// <param name="expiration">Длительность хранения.</param>
void AddOrUpdate<T>(string key, Func<T, T> updater, TimeSpan expiration) where T : class;
/// <summary>
/// Добавление значения в кеш.
/// </summary>
/// <typeparam name="T">Тип данных.</typeparam>
/// <param name="key">Ключ данных.</param>
/// <param name="updater">Фуктор для обновления значений.</param>
void AddOrUpdate<T>(string key, Func<T, T> updater) where T : class;
/// <summary>
/// Получение значения из кеша.
/// </summary>
/// <typeparam name="T">Тип объекта.</typeparam>
/// <param name="key">Ключ данных.</param>
/// <returns>Данные из кеша.</returns>
T Get<T>(string key) where T : class;
/// <summary>
/// Удаляет значение из кеша.
/// </summary>
/// <param name="key">Ключ.</param>
void Remove(string key);
}
}
using System;
using System.Runtime.Caching;
namespace Cache.Implementation
{
/// <summary>
/// Простой in-memory кеш на основе стандарных механизмов <see cref="ObjectCache"/>.
/// </summary>
public class SimpleCacheManager : ICacheManager
{
#region Properties
/// Объектный кеш.
public ObjectCache ObjectCache { get; private set; }
/// <summary>
/// Количество секунд, которые хранятся в кеше значения.
/// </summary>
public int DefaultCacheDuration { get; set; } = 15 * 60;
/// <summary>
/// Сдвиг от текущего времени на момент истечения данных в кеше.
/// </summary>
private DateTimeOffset DefaultExpiryDateTime
=> DateTimeOffset.Now.AddSeconds(DefaultCacheDuration);
#endregion
#region Constructors
public SimpleCacheManager()
: this(MemoryCache.Default)
{ }
public SimpleCacheManager(ObjectCache objectCache)
{
ObjectCache = objectCache;
}
#endregion
#region ICacheManager
/// <inheritdoc />
public T GetOrAdd<T>(string key, Func<T> factory, TimeSpan expiration) where T : class
{
return GetOrAddImpl(key, factory, GetSlidingExpirationCachePolicy(expiration));
}
/// <inheritdoc />
public T GetOrAdd<T>(string key, Func<T> factory) where T : class
{
return GetOrAddImpl(key, factory, GetAbsoluteExpirationCachePolicy(DefaultExpiryDateTime));
}
/// <inheritdoc />
public void Add<T>(string key, T value, TimeSpan expiration) where T : class
{
AddImpl(key, value, GetSlidingExpirationCachePolicy(expiration));
}
/// <inheritdoc />
public void Add<T>(string key, T value) where T : class
{
AddImpl(key, value, GetAbsoluteExpirationCachePolicy(DefaultExpiryDateTime));
}
/// <inheritdoc />
public T Get<T>(string key) where T : class
{
return GetImpl<T>(key);
}
/// <inheritdoc />
public void AddOrUpdate<T>(string key, Func<T, T> updater, TimeSpan expiration) where T : class
{
AddOrUpdateImpl(key, updater, GetSlidingExpirationCachePolicy(expiration));
}
/// <inheritdoc />
public void AddOrUpdate<T>(string key, Func<T, T> updater) where T : class
{
AddOrUpdateImpl(key, updater, GetAbsoluteExpirationCachePolicy(DefaultExpiryDateTime));
}
/// <inheritdoc />
public void Remove(string key)
{
RemoveImpl(key);
}
#endregion
#region Private methods
/// <summary>
/// Имплементация для метода добавления данных в кеш.
/// </summary>
private void AddImpl<T>(string key, T value, CacheItemPolicy policy)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
EnsureValidKey(key);
ObjectCache.Set(key, value, policy);
}
/// <summary>
/// Имплементация для метода получения/добавления данных из/в кеш.
/// </summary>
private T GetOrAddImpl<T>(string key, Func<T> factory, CacheItemPolicy policy) where T : class
{
EnsureValidKey(key);
UnwrapLazyFromRemovedCallback<T>(policy);
var existingValue = ObjectCache.AddOrGetExisting(key, CreateLazy(factory), policy);
if (existingValue != null)
// Т.к. в кеше хранятся "лентяи"
// то нужно их сначала распаковать.
return UnwrapLazy<T>(existingValue);
try
{
return factory();
}
// Ошибка при вызове factory()
// Пусть кто-то другой разбирается с исключениями.
catch
{
ObjectCache.Remove(key);
throw;
}
}
/// <summary>
/// Имплементация для метода получения данных из кеша.
/// </summary>
private T GetImpl<T>(string key)
{
EnsureValidKey(key);
return UnwrapLazy<T>(ObjectCache[key]);
}
/// <summary>
/// Имплементация для метода добавления или обновления данных в кеше.
/// </summary>
private void AddOrUpdateImpl<T>(string key, Func<T, T> updater, CacheItemPolicy policy) where T : class
{
EnsureValidKey(key);
UnwrapLazyFromRemovedCallback<T>(policy);
var existingValue = ObjectCache.Get(key);
Func<T> resultFactory = () => (existingValue != null)
? updater(UnwrapLazy<T>(existingValue))
: updater(null);
ObjectCache.Add(key, CreateLazy(resultFactory), policy);
}
/// <summary>
/// Имплементация для метода удаления данных из кеша.
/// </summary>
private void RemoveImpl(string key)
{
EnsureValidKey(key);
ObjectCache.Remove(key);
}
/// <summary>
/// Валидация ключа для кеша.
/// </summary>
/// <param name="key">Ключ.</param>
private void EnsureValidKey(string key)
{
if (key == null)
throw new ArgumentNullException(nameof(key));
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentOutOfRangeException(nameof(key), "Ключ не может быть пустым");
}
/// <summary>
/// Возвращает политику кеширования с относительным сдвигом времени хранения.
/// </summary>
private CacheItemPolicy GetSlidingExpirationCachePolicy(TimeSpan expitation)
{
return new CacheItemPolicy { SlidingExpiration = expitation };
}
/// <summary>
/// Возвращает политику кеширования с абсолютным сдвигом времени хранения.
/// </summary>
private CacheItemPolicy GetAbsoluteExpirationCachePolicy(DateTimeOffset offset)
{
return new CacheItemPolicy { AbsoluteExpiration = offset };
}
/// <summary>
/// Получение значения из Lazy-объекта.
/// </summary>
/// <typeparam name="T">Тип значения.</typeparam>
/// <param name="item">Объект для распаковки.</param>
/// <returns>Значение.</returns>
private T UnwrapLazy<T>(object item)
{
var lazy = item as Lazy<T>;
if (lazy != null)
return lazy.Value;
if (item is T)
return (T)item;
return default(T);
}
/// <summary>
/// Переопределение removed-колбека политики кеша таким образом,
/// чтобы он не работал с Lazy{T} объектом.
/// </summary>
/// <typeparam name="T">Тип объекта.</typeparam>
/// <param name="policy">Политика кеша.</param>
private void UnwrapLazyFromRemovedCallback<T>(CacheItemPolicy policy)
{
if (policy?.RemovedCallback == null)
return;
var callback = policy.RemovedCallback;
policy.RemovedCallback = args =>
{
var item = args?.CacheItem?.Value as Lazy<T>;
if (item != null)
args.CacheItem.Value = item.IsValueCreated ? item.Value : default(T);
callback(args);
};
}
/// <summary>
/// Заворачивает фабрику значений в ленивый объект.
/// </summary>
/// <typeparam name="T">Тип значений.</typeparam>
/// <param name="factory">Фабрика значений.</param>
/// <returns>Ленивый объект.</returns>
private Lazy<T> CreateLazy<T>(Func<T> factory)
=> new Lazy<T>(factory);
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment