Caching, like other utility code, can often clutter your nice clean methods. So why not tuck it away behind a user friendly API? With bonus points for a “fluent interface” :)
using System;
namespace Your.Namespace.Here
{
public interface ICache
{
ICacheLifespan<T> Cache<T>(Func<T> factory);
}
public interface ICacheLifespan<T>
{
ICacheKeying<T> For(TimeSpan timeToLive);
}
public interface ICacheKeying<T>
{
T WithKey(string cacheKey);
}
public class InMemoryCache : ICache
{
private readonly ObjectCache _cache;
public InMemoryCache(ObjectCache cache)
{
_cache = cache;
}
public ICacheLifespan<T> Cache<T>(Func<T> factory)
{
return new CacheLifespan<T>((ttl, k) =>
{
if (_cache.Contains(k))
return (T)_cache[k];
var value = factory();
var cacheItemPolicy = new CacheItemPolicy
{
AbsoluteExpiration = DateTimeOffset.Now.Add(ttl)
};
_cache.Add(k, value, cacheItemPolicy);
return value;
});
}
}
public class CacheLifespan<T> : ICacheLifespan<T>
{
private readonly Func<TimeSpan, string, T> _factory;
public CacheLifespan(Func<TimeSpan, string, T> factory)
{
_factory = factory;
}
public ICacheKeying<T> For(TimeSpan timeToLive)
{
return new CacheKeying<T>(timeToLive, _factory);
}
}
public class CacheKeying<T> : ICacheKeying<T>
{
private readonly TimeSpan _timeToLive;
private readonly Func<TimeSpan, string, T> _factory;
public CacheKeying(TimeSpan timeToLive, Func<TimeSpan, string, T> factory)
{
_timeToLive = timeToLive;
_factory = factory;
}
public T WithKey(string cacheKey)
{
return _factory(_timeToLive, cacheKey);
}
}
public class Examples
{
private readonly InMemoryCache inMemoryCache = new InMemoryCache(new ObjectCache());
[Test]
public void Fluent_caching()
{
inMemoryCache.Cache(() => CreateExpensiveObject())
.For(TimeSpan.FromHours(1))
.WithKey("cacheKey");
}
}
}