Last active
May 9, 2017 20:34
-
-
Save btshft/06fe4be53bc588df29c7dd97e787fbfd to your computer and use it in GitHub Desktop.
Simple cache manager for .Net
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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