I am a big fan of using Lazy<T> as my go-to implementation of the Singleton pattern. It works well and the thread safety is easily adjustable . While I won’t go into the details of how to use it, all the information is in the Lazy<T> documentation.
For what I needed, however, Lazy<T> fell a bit short. I essentially needed a cache of something that was expensive to create, and could be invalidated when the source object changed. In my case, I had a list that I needed to run some aggregate queries against. Running those queries is expensive, so I only want to re-run them when the list has changed, and I want to run them on demand (don’t compute until the last second when it is needed). In other words, I needed a really simple cache.
Thinking it to be easy enough to implement, I created one, using a Lazy<T> as the implementation. My class, the LazyCache<T>, behaves as you would expect Lazy<T> to behave, but with an additional method: Invalidate(). Please note that I didn’t test the thread safety of the LazyCache<T> and already see some potential problems, so if that is a requirement please take steps to ensure that it will behave as needed.
LazyCache<T> Usage
Here is some demo code that uses the LazyCache<T>
[Test] public void LazyCache_DemonstrationWithObservableCollection() { var data = new ObservableCollection<string>() { "Hi", "Howru" }; Func<Dictionary<string, int>> dataAsDic = () => data.ToDictionary(x => x, x => x.Length); var lazy = new LazyCache<Dictionary<string, int>>(dataAsDic); data.CollectionChanged += (sender, e) => lazy.Invalidate(); Assert.AreEqual(lazy.Value.Count, 2); // "Hi", "Howru" data.Add("Tree"); Assert.AreEqual(lazy.Value.Count, 3); // "Hi", "Howru", "Tree" data.RemoveAt(0); // "Howru", "Tree" data.RemoveAt(0); // "Tree" Assert.AreEqual(lazy.Value.Count, 1); // "Tree" }
LazyCache<T> Implementation
And here is my implementation of LazyCache<T>:
public class LazyCache<T> { private readonly Func<Lazy<T>> lazyFactory; private Lazy<T> lazy; private Lazy<T> Lazy { get { if (lazy == null) { lazy = lazyFactory(); } return lazy; } } public LazyCache() { lazyFactory = () => new Lazy<T>(); } public LazyCache(bool isThreadSafe) { lazyFactory = () => new Lazy<T>(isThreadSafe); } public LazyCache(Func<T> valueFactory) { lazyFactory = () => new Lazy<T>(valueFactory); } public LazyCache(LazyThreadSafetyMode mode) { lazyFactory = () => new Lazy<T>(mode); } public LazyCache(Func<T> valueFactory, bool isThreadSafe) { lazyFactory = () => new Lazy<T>(valueFactory, isThreadSafe); } public LazyCache(Func<T> valueFactory, LazyThreadSafetyMode mode) { lazyFactory = () => new Lazy<T>(valueFactory, mode); } public bool IsValueCreated { get { return Lazy.IsValueCreated; } } public T Value { get { return Lazy.Value; } } public void Invalidate() { lazy = null; } }